一句话结论:让大模型一次产出上万字(分集大纲、长篇小说、长翻译、大段代码)时,用流式、不要用非流式;客户端 read timeout 按「两次数据事件之间的间隔」设(几十秒即可,建议 90120 秒),不要按「整段生成的总时长」设;3 万字时,再加一条:拆成多次生成,别让一次连接跑 20 分钟。
max_tokens 给足;拿到响应后先看 stop_reason 再用正文。做到这四点,长文场景就不会「拿不到结果」。输出超过 2/v1/messages 为主,OpenAI 兼容格式的差异单独标注。
三个先知道的事实
- 出万字长文,真实生成 10~20 分钟是常态。模型要逐 token 产出上万字,叠加推理/思考阶段,端到端耗时本就很长。这不是网关慢,是生成本身慢。
-
非流式要「整段攒齐」才回写。非流式(
stream不传或为false)下,服务端必须等模型把整段生成完,再一次性把响应体回传给你。这几分钟里你的客户端 read timeout 一直在和它赛跑,生成越久越容易在拿到结果前先断开——断开时异常信息经常是空的(httpx.ReadError的str(e)为空),看不出病因。 - 断连仍然计费,盲目重试是重复计费。只要服务端已经产出,哪怕最后没送达你,这次调用也照常计费。已经收到部分正文再断开的情况,重试等于让模型再跑一遍、再付一次钱。
用流式,不要用非流式
流式(stream: true)下,首字节几秒内就到,之后每隔几十秒必有一个数据事件。你的 read timeout 只需覆盖「两次事件之间的间隔」,而不是覆盖长达十几分钟的整段生成——这是流式能稳定拿到长文结果的根本原因。
两种协议的结束信号不同,别混用:
Claude 原生开启自适应思考时,会先出一个
type: "thinking" 的思考块(增量是 thinking_delta),再出 text 正文块。渲染时把 thinking_delta 和 text_delta 分流即可,思考增量不拼进正文。
Claude 原生 /v1/messages 流式最小可用示例(纯 httpx,逐行解析 SSE):
read timeout 按事件间隔设,不是按总时长
很多人把 read timeout 设成一个能兜住整段生成的巨大值(比如 1800 秒),结果照样超时——因为非流式下这个值要和整段生成竞速,稍有波动就断。正确做法是流式 + 按事件间隔设 read timeout。 实测参考(claude-opus-5 出约 2 万字分集大纲,输入约 1.5 万字符):
所以 read timeout 设 90 ~ 120 秒足以覆盖最大事件间隔并留余量,不必设成十几分钟。三段式超时把三个阶段拆开,各自设值:
各语言客户端怎么设
不少客户端库同时有「单次读超时」和「总超时」两个参数,名字还很像。长文场景要设的是前者,后者不设或设成不限:流式也会卡住:连接中途停止接收
流式解决了「整段攒齐才回写」的问题,但还有一种失败方式要防:连接中途卡住。- 现象:开头正常出字,之后再也收不到数据,连接却既不报错也不断开。客户端一直挂到自己的超时上限:设的是 1 小时,就整整等 1 小时,最后只拿到开头一小段。
- 原因:在跨地域、跨境的网络链路上,长时间不断开的连接可能被中间的网络设备卡住。服务端早已生成完毕并关闭连接,但关闭信号没能传到你这边。连接持续越久,越容易遇到:20 分钟的长连接比 3 分钟的更容易中招。
- 计费:服务端照常生成完,照常计费(同「三个先知道的事实」第 3 条)。
- read timeout 按事件间隔设(上一节)。同样是卡住,读超时 120 秒只白等 2 分钟就能报错重试,读超时 1 小时就要白等 1 小时。这是最关键的一条。
- 缩短单次连接:超长输出拆成多次生成,见下文「超长输出拆成多次生成」。
- 断流后从断点续写,见下文「重试策略」。
max_tokens 给足,并检查 stop_reason
长输出容易撞到max_tokens 上限被截断。尤其是 Claude 这类开了思考的模型,思考本身也占 max_tokens 预算,一份长文很容易把预算吃满。
max_tokens建议 64000 起步(开高 effort / 深度思考时更要给足;claude-opus-5输出上限 128K)。- 拿到响应先看
stop_reason:end_turn——正常结束,正文完整,这才算成功。max_tokens——被截断,正文可能不完整甚至为空。这是被截断不是「空结果」,把max_tokens调大后重试即可。refusal——被安全策略拒绝,单独处理。
str(e) 或「正文为空」来判断成败会误导——空正文的真实原因往往是 max_tokens 截断。
超长输出拆成多次生成
一次让模型出 5 万字、跑 20 分钟,即使用了流式也是最脆弱的用法:中途任何一次网络波动,整段作废、整段计费。输出量超过 2~3 万字时,建议拆开生成:- 先生成大纲 / 分集列表 / 章节目录,这是个短请求。
- 再按章节或分集逐段生成,每次控制在 1~2 万字以内。每次请求都带上大纲和必要的前文信息(人物设定、上一段结尾),保证前后连贯。
- 大纲定下来之后,各段相互独立,可以并行发出。
重试策略
长文场景的重试要克制,别让「失败重试」变成「重复计费 + 重复长跑」:- 只对「拿到响应头之前的失败」和
5xx/429重试(退避、最多 2 次)。这类是建连/瞬时问题,重试有意义。 - 已经收到部分正文再断流的,从断点续写,不要从头重来。服务端已经产出并计费,从头重试是让它再跑一遍、再付一次钱。把已收到的正文保留下来,在新请求里作为已写内容附上,让模型接着写,只为剩下的部分付费。
- read timeout 触发(连接卡住)也按断流处理:已有不少正文就续写;几乎没收到正文,再整段重试。
- 记录响应头里的 request id,方便对账和排查。
场景速查
单篇超过 3 万字的,建议按章节或分集拆成多次生成(见「超长输出拆成多次生成」)。全部走流式;节点用
api.apiyi.com(或备用 b.apiyi.com),不要用 api-cf.apiyi.com(CDN 节点约 100 秒就 524,扛不住长请求)。
相关链接
如何避免接口超时
分场景的 timeout 推荐值
流式 vs 非流式
两种模式的差异与选型
Claude 思考与 effort
自适应思考、effort 档位、max_tokens 与截断
Claude 响应处理
原生响应结构、SSE 事件、stop_reason