会话留在服务端:Anthropic Message Threads 拿走了什么,又把什么推回给你

举报
yd_264084165 发表于 2026/11/04 15:06:08 2026/11/04
【摘要】 会话留在服务端:Anthropic Message Threads 拿走了什么,又把什么推回给你【摘要】11 月 2 日,Anthropic 给 Messages API 开了一个叫 Message Threads 的公测。你第一次发完整对话,服务端把它存下来;之后每轮只发新增的那几条。Anthropic 说 50 万到 100 万 token 的请求,首字延迟中位数降了约 35%。听起来...

会话留在服务端:Anthropic Message Threads 拿走了什么,又把什么推回给你

【摘要】11 月 2 日,Anthropic 给 Messages API 开了一个叫 Message Threads 的公测。你第一次发完整对话,服务端把它存下来;之后每轮只发新增的那几条。Anthropic 说 50 万到 100 万 token 的请求,首字延迟中位数降了约 35%。听起来像白捡的性能,但读完官方文档我发现三件事:token 用量一点没少;客户端必须自己留一份完整副本;而 5 分钟的存活期,跟 agent 干活的节奏正面冲突。这篇讲清楚它到底省了什么、没省什么,以及什么样的 agent 用得上、什么样的用不上。

目录

一、一个把"每轮重发"变成"只发增量"的 API
二、它省的是上传,不是阅读
三、35% 这个数字是怎么来的,以及它对谁成立
四、最容易被忽略的那条:客户端才是真相之源
五、5 分钟 TTL:一个 agent 循环的死穴
六、fingerprint 一致性,和"按任务调档"的冲突
七、还有一堆不能用的地方
八、怎么判断你的 agent 该不该上

一、一个把"每轮重发"变成"只发增量"的 API

先说清楚 Messages API 原来的样子:它是无状态的。

不管你和模型聊了 50 轮还是 500 轮,每一次调用,你都要把从第一句 user 消息开始的全部历史重新发一遍——包括模型自己说过的每一句、每一个 tool_use、每一个 tool_result。服务端收到这一大坨,重新校验、重新 tokenize,然后才轮到模型开始思考。

Anthropic 这次的改法很简单:加一个 thread 参数。

POST /v1/messages
anthropic-beta: message-threads-2026-08-12

{
  "model": "claude-opus-5-5",
  "system": "...",          // 只在第一次发
  "tools": [...],           // 只在第一次发
  "messages": [...],        // 第一次:完整对话;之后:只发新增
  "thread": {"type": "create"}   // 第一次
}

第一次请求带 {"type": "create"},服务端把整个对话(含 Claude 自己的回复)存在响应 id 下。之后每次只发新增消息,带上 {"type": "continue", "previous_message_id": "<上一次响应的 id>"},system 和 tools 要省略——服务端留着。

就这么点事。但它落在一个很具体的痛点上:一个 coding agent 跑一次会话可能有几百次请求,每次都把前面所有 tool_result 重新传一遍。累积到几十万 token 时,光是"传上去 + 重新 tokenize"这段就成了首字延迟的主要来源。

Anthropic 给的数字是:50 万到 100 万 token 的请求,首字延迟中位数下降约 35%。

二、它省的是上传,不是阅读

这是整件事里最容易被误读的一点,官方文档写得很直白:

While each request is smaller with message threads, Claude still reads the full conversation on every turn, so token usage stays the same.

请求变小了,但模型读的还是全部。

也就是说,省掉的是:网络传输时间、服务端重复的 tokenize 开销、前缀重复解析的开销。没省掉的是:模型侧的推理成本、上下文占用的显存、以及按 token 计的费用。

这跟 prompt cache 是两个正交的层。Prompt cache 省的是"前缀不用重算";threads 省的是"前缀不用重传"。你可以两个一起用——文档里顶层 cache_control 允许每轮带,而且要求每轮值一致。

但这里有个必须说清楚的后果:如果你原来靠"每轮重发"来自动续上 prompt cache,上了 threads 之后这个隐式行为没了。 你得显式管理 cache_control 的位置和生命周期。少一个东西,通常意味着多一个你要自己管的东西。

三、35% 这个数字是怎么来的,以及它对谁成立

Anthropic 的原话有一个限定条件:

with message threads on by default for most Claude Code users, requests with 500k to 1M tokens see median TTFT drop by about 35%

三个限定词:默认开启(不是所有人)、50 万到 100 万 token(不是所有规模)、中位数(不是平均数也不是最坏情况)。

几个推论:

  • 上下文小的 agent 基本感觉不到。 几千 token 的对话,重传的量本来就小,节省自然也小。文档自己说"the more context an agent carries, the more message threads can reduce time to first token"。
  • 上下文大的 agent 收益大,但那些 agent 恰恰是最容易撞上 TTL 的(后面第五节会说)。
  • 中位数下降 35% 意味着长尾没改善。 如果你的场景是偶发一个超长请求,前几次延迟可能反而更难看(要建 thread)。

另外有一条被很多人忽略的官方提示:Anthropic 没有给出 threads 单独的 TTFT 数字,"35%“是"默认开启前后"的对比,不是"threads 开 vs 关”。这个区别在你自己做 A/B 的时候要注意。

四、最容易被忽略的那条:客户端才是真相之源

官方文档里有一句话,位置很靠后,但它是整个设计的地基:

Your application stays the source of truth for the conversation. Keep your own copy of every message, and build the replay path from the start.

翻译过来:服务端那个 thread 是缓存,不是存储。你必须自己在本地留一份完整对话。

原因很实在——线程是 best-effort 的。它会过期,会被删,可能在服务端某个时刻消失。而文档明确说了:

No response, to a create or a continue request, says whether the API stored that turn. You find out on the next continue request.

你无法知道服务端到底存没存。 只有下一次 continue 请求的时候才会通过 404 告诉你。

这带来两个具体的坑:

第一,SDK 的 helper 只在内存里存。 文档说得很清楚:SDK thread helper “keeps this copy in memory while the helper object exists, and saves it nowhere else”。进程一挂,你的"真相之源"就没了。 如果对话要跨进程、跨机器、断点续跑,你得自己实现持久化。

第二,replay 路径要一开始就搭好。 不是出错了再补——是第一次写代码时就把"完整重发"这条路径做出来。我见过太多"先跑通再补容错"的代码,最后补不上去,因为原始对话根本没存。

五、5 分钟 TTL:一个 agent 循环的死穴

线程的寿命规则:

  • 最后一次请求后 5 分钟过期
  • 最后一次使用后不超过 1 小时删除
  • 每存一个 response 就重置那 5 分钟

对于一个交互式对话,这够用。你说一句,它答一句,间隔不会超过 5 分钟。

但 agent 不是这样工作的。

一个 coding agent 的典型循环是:读文件 → 改代码 → 跑测试 → 测试跑了 8 分钟 → 看到失败 → 改代码 → 再跑测试。

那 8 分钟里,它一次请求都没发。线程在第 5 分钟过期。等它跑完测试回来要发下一个请求时,拿到的是 404。

404 之后怎么办?重发完整对话。

所以你会看到一个很荒谬的场景:agent 干了 20 分钟活,每次超过 5 分钟的工具调用之后都要把整个上下文重新传一遍。 而这恰恰是 threads 最应该帮上忙的场景——恰恰是那种"上下文大、工具调用多"的长任务。

这不是 bug,这是 TTL 和 agent 工作节奏之间的结构性冲突。文档也承认了这点,说线程"best for active sessions where latency matters"。

所以我判断 threads 的真实适用面比公告暗示的窄:它最适合"高频短间隔"的 agent 循环(比如每轮就是一个 tool call,间隔几秒),不适合"跑长任务、长时间不通信"的 agent。

绕过的办法有两个:把长任务拆碎,每 5 分钟内插一次请求;或者自己实现服务端缓存。

六、fingerprint 一致性,和"按任务调档"的冲突

这是文档里最容易被忽略、但对实际开发最要命的一条规则。

thread 创建时,有一批字段被锁定了:

分类 字段 规则
只在 create 发 system, tools continue 上发送(哪怕是 null)→ 400
每轮必须一致 model, tool_choice, thinking, mcp_servers(除授权 token), context_management, output_config, container 的 skills, 顶层 cache_control, anthropic-beta 值 值不同 → 400 thread_fingerprint_mismatch,错误消息会指出哪个字段不同
自由可变 max_tokens, stop_sequences, stream, metadata, service_tier 等 每轮可改

先看"只在 create 发"这条。直觉上"每轮都发一遍 system 和 tools 最保险",但发了就 400——哪怕发的是 null。这跟大多数 API 的直觉相反。

再看第二条,这条是真冲突。

thinking 在锁定列表里。model 也在。

而现在的 agent harness,一个非常常见的做法是根据任务难度调整推理档位——简单任务用低档省成本,复杂任务用高档保质量。或者根据错误类型换模型。

这两个需求跟 threads 直接冲突。 你想在第二轮把 thinking 从 low 调到 high,拿到的是 400 thread_fingerprint_mismatch。

这个错误设计得还算友好——它告诉你哪个字段不一样。但它把一个决策提前了:如果你要按任务调档位,你得在建 thread 之前就知道档位,而且不能中途改。

(顺便说,max_tokens 是自由的,所以"输出多长"可以调,"想多久"不能调。这两个在实践中经常被混为一谈。)

七、还有一堆不能用的地方

文档专门列了一节"不可用",信息量不小:

和 compaction 互斥。 按需压缩对话的 compaction 参数不能用,compact-2026-09-04 beta 头也不能用。也就是说,长对话的主动压缩这条路暂时被堵死了。而 threads 的目标场景恰恰是长对话——上下文压缩和 thread 是两种不同的长对话解法,现在你只能选后者。

token counting 不支持。 你没法问"这个 thread 现在多少 token 了",只能自己数完整对话。

Message Batches 不支持。 批处理里带 thread 的项会报 invalid_request_error。

context editing 大部分不支持(只留 clear_thinking_20251015 配 keep: "all")。

HIPAA readiness 的组织用不了。开了 Inference hooks 的组织也用不了。

没有管理 API。 不能列出、读取或删除一个 thread。这意味着你没法审计服务端到底存了你什么,也没法主动清理。

还有两个行为细节容易踩:

  • thread 是 append-only 的,每个 response id 只能被续一次,而且必须从最新的 id 续。想编辑早期消息或从中间分支,只能开新 thread。
  • 重复发消息会重复出现。API 不检查你发的消息是否和已有的重复。文档给的例子是"以 previous response id 开头的 continue 请求会报错"——但报错的是这个特例,不是通例。客户端必须自己保证增量正确。

八、怎么判断你的 agent 该不该上

三个问题,对应三个门槛:

第一,你的 agent 会不会有超过 5 分钟不通信的空窗?
会——比如跑长测试、跑构建、等外部 API——那 threads 的价值会被 TTL 吃掉大半。先量化一下你的 agent 一次会话里的最长空窗有多长。

第二,你的上下文有多大?
几千 token 的,收益接近零。上 threads 之前先看一眼你的典型请求有多大。文档给的收益区间是 50 万到 100 万 token,那已经是非常长的 agent 会话了。如果你还没到这个量级,先把上下文管理做好,比优化传输划算得多。

第三,你的 agent 会不会中途换模型或调推理档位?
会——那 fingerprint 规则会直接卡住你。要么放弃 threads,要么把决策提前。

如果三个都是"不会",那 threads 对你几乎是纯收益:少传很多数据,首字延迟降一截,token 账单一分不变。

最后提醒一句实现顺序:先做 replay 路径,再开 threads。 官方文档把这条写在"从第一天就搭好"的位置上,我认为是整篇文档最实用的一句。它不是可选项——它是 threads 能安全使用的前提。


一句话:Message threads 把"每轮重发"这个动作从客户端挪到了服务端,省掉了上传和 tokenize,但没省 token、没省模型的阅读、也没省你维护状态的义务。它是一次干净的架构改良,不是性能魔法。

留言区:你在用 threads 吗?你的 agent 一次会话最长有多久不通信?如果踩过 thread_fingerprint_mismatch,说说当时想调的是什么。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。