凌晨 2 点,一个 AI 写作平台的用户正在生成一篇 5 万字的长篇小说。SSE 流已经推送了 80% 的内容,进度条走到了最后几段。然后连接断了------可能是 TCP 超时,可能是负载均衡器主动断连,也可能只是用户的 Wi-Fi 闪了一下。
客户端拿到一个不完整的响应,只能从头重试。用户又等了 3 分钟,看到的是「生成失败,请重试」。
这不是假设场景。在我们团队运营的一个 AI 内容平台上,日均 50 万次流式调用中,128K token 输出场景的断流率超过 8%。而社区里的开发者讨论(Hacker News 2026 年的一个热门帖子)显示,50K+ token 的长文本生成中约 12% 经历至少一次连接断开。
问题不在于断流本身------网络不可靠是常态。问题在于 90% 的团队在断流后只会做一件事:丢弃已收到的所有内容,从头发起一个新请求。已生成的 4 万字 token 全部浪费,用户多等 3 分钟,服务端白白多跑一次完整推理。
本文把我们在过去 6 个月里踩过的坑和验证过的 5 种断流恢复策略整理出来,从最简单的 transport-level retry 到生产级的 semantic resume,每种方案都附带可运行的代码和真实场景下的取舍分析。
一、为什么 LLM Streaming 断流恢复这么难
先搞清楚问题再谈方案。LLM 的流式响应和普通 HTTP 流式下载有本质区别,这也是为什么 SSE 协议原生的重连机制在 LLM 场景下几乎用不上。
1.1 SSE 协议的 Last-Event-ID 为什么不够
SSE(Server-Sent Events)规范确实内置了断点续传能力。客户端断连后重连时,浏览器会自动带上 Last-Event-ID 请求头,服务端理论上可以从这个 ID 继续推送事件。
kotlin
// SSE 规范中的重连机制
// 客户端自动发送:
GET /stream HTTP/1.1
Last-Event-ID: evt-4287
// 服务端从 evt-4288 继续推送
data: {"id":"evt-4288","delta":"继续生成的内容..."}
这在传统场景(日志推送、股票行情)下完全够用。但 LLM streaming 有三个特殊性让这套机制失效:
推理有状态,服务端不缓存。 模型推理是逐 token 生成的,服务端通常不会把已生成的 token 缓存在内存里等你重连。一旦推理协程结束(无论正常还是异常),服务端的上下文就释放了。
事件 ID 不等于生成进度。 LLM streaming 的 chunk 粒度不固定------一个 chunk 可能包含 1 个 token,也可能包含 5 个。Last-Event-ID 告诉你从哪个事件继续,但服务端已经没有那个事件之后的内容了。
模型不能从中间 token 继续。 即使你告诉模型「从第 4287 个 token 继续生成」,模型也不知道前面生成了什么。自回归模型需要完整的上下文前缀才能生成下一个 token。
1.2 三大断流场景
在生产环境中,我们把 LLM streaming 的断流分为三类,每类的发生频率和应对策略不同:
| 断流类型 | 常见原因 | 发生频率 | 可预测性 |
|---|---|---|---|
| 网络层断开 | TCP 超时、中间设备 RST、Wi-Fi/4G 切换 | 高(占断流 60%+) | 低,随机发生 |
| 服务端中断 | GPU OOM、推理超时、LB 主动断连 | 中(占断流 25%) | 部分可预测 |
| 客户端中断 | 用户刷新页面、标签休眠、App 切后台 | 中(占断流 15%) | 完全不可预测 |
网络层断开是最常见的,也是断流恢复收益最大的场景------因为此时服务端可能还在正常推理,只是推送不到客户端了。
1.3 现有 SDK 的处理方式
主流大模型 SDK 的处理方式大多停留在 transport-level retry。以某主流大模型厂商的 Python SDK 为例:
python
# 某主流大模型厂商 Python SDK 的做法(简化)
class Stream:
def __iter__(self):
while True:
try:
for chunk in self._stream():
yield chunk
break
except (ConnectionError, TimeoutError):
# 自动重连,但从头开始
self._reconnect()
这能避免连接层面的失败,但语义层面------已生成的内容------全部丢失。对于短响应(<1000 token),从头重试的成本可以接受。但对于长文本生成,这个浪费是不可接受的。
二、5 种断流恢复策略
按实现复杂度从低到高,以下是我们在生产中验证过的 5 种策略。每种策略独立可用,也可以组合使用。
策略 1:Transport-Level Retry(基线方案)
这是最简单的方案,也是大多数团队当前的做法。核心思路:连接断了就重试,但重试时把已收到的 partial response 保留下来。
python
import time
def stream_with_retry(
client,
messages: list,
max_retries: int = 3,
model: str = "deepseek-chat"
):
"""基线方案:断流后重试,但保留已收到的内容"""
collected = []
for attempt in range(max_retries + 1):
try:
stream = client.chat.completions.create(
model=model,
messages=messages,
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
collected.append(delta)
# 正常结束,返回完整内容
return "".join(collected)
except (ConnectionError, TimeoutError) as e:
if attempt == max_retries:
# 最后一次重试也失败了,返回已收集的部分内容
partial = "".join(collected)
return partial if partial else None
# 指数退避
time.sleep(min(2 ** attempt, 10))
return None
优点: 实现简单,零额外依赖,适用于任何大模型 API。
局限: 重试时从头开始生成,已收集的 partial response 和新请求的响应无法拼接。你只能选择「丢弃已收集内容,用新响应替换」或「保留已收集内容,放弃剩余生成」。前者浪费 token,后者内容不完整。
适用场景: 短响应(<2000 token)、对完整性要求不高的场景(如聊天机器人回复)。
策略 2:Chunk-Level Checkpoint(推荐方案)
这是投入产出比最高的方案,也是我们在大多数场景下的默认选择。核心思路:每收到 N 个 chunk,就把当前累积的内容持久化到 Redis 或数据库。断流后从最后一个 checkpoint 恢复。
python
import redis
import json
import hashlib
from datetime import datetime
class StreamingCheckpoint:
def __init__(self, redis_client: redis.Redis, session_id: str):
self.r = redis_client
self.session_id = session_id
self.key = f"stream:ckpt:{session_id}"
self.ttl = 3600 # 1 小时过期
def save(self, content: str, chunk_count: int, usage: dict = None):
"""持久化当前进度"""
data = {
"content": content,
"chunk_count": chunk_count,
"usage": usage or {},
"updated_at": datetime.now().isoformat()
}
self.r.setex(self.key, self.ttl, json.dumps(data))
def load(self) -> dict | None:
"""恢复上一次的进度"""
raw = self.r.get(self.key)
if raw:
return json.loads(raw)
return None
def clear(self):
"""生成完成后清理"""
self.r.delete(self.key)
def stream_with_checkpoint(
client,
messages: list,
session_id: str,
checkpoint_every: int = 50,
model: str = "deepseek-chat"
):
"""带 checkpoint 的流式生成"""
ckpt = StreamingCheckpoint(redis.Redis(), session_id)
# 检查是否有未完成的 checkpoint
existing = ckpt.load()
if existing:
collected = [existing["content"]]
start_chunk = existing["chunk_count"]
# 将已生成内容加入 messages,让模型从断点继续
messages = messages + [
{"role": "assistant", "content": existing["content"]},
{"role": "user", "content": "请从上次中断的地方继续。"}
]
else:
collected = []
start_chunk = 0
chunk_count = start_chunk
try:
stream = client.chat.completions.create(
model=model,
messages=messages,
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
collected.append(delta)
chunk_count += 1
# 每 N 个 chunk 做一次 checkpoint
if chunk_count % checkpoint_every == 0:
ckpt.save("".join(collected), chunk_count)
# 正常完成
result = "".join(collected)
ckpt.clear()
return result
except (ConnectionError, TimeoutError):
# 断流了,但 checkpoint 已经保存了当前进度
# 下次调用 stream_with_checkpoint 时会自动恢复
ckpt.save("".join(collected), chunk_count)
raise StreamingInterruptedError(
session_id=session_id,
partial_content="".join(collected),
chunks_received=chunk_count
)
核心设计点:
- Checkpoint 频率: 每 50 个 chunk 存一次,平衡写入开销和恢复粒度。对于长文本,可以适当降低到每 20 个 chunk。
- 恢复方式: 不是「从断点续传 SSE」,而是「把已生成内容作为上下文前缀,让模型继续生成」。这比尝试续传 SSE 流可靠得多。
- Session ID: 用业务维度的 ID(如用户+任务ID),方便跨请求关联。
优点: 断流后不丢失已生成内容,恢复延迟低(只需重新建立连接),token 浪费控制在 checkpoint 间隔以内。
局限: 需要一个 Redis 实例;恢复时模型的续写质量取决于 prompt 设计(「请从上次中断的地方继续」不一定能保证无缝衔接)。
适用场景: 绝大多数 LLM 流式应用,特别是长文本生成、AI 写作、代码生成。
策略 3:SSE Resume Offset
这个方案需要服务端配合,适用于自建 LLM Gateway 的团队。核心思路:服务端为每个流式响应维护一个 offset 索引,客户端断连重连时带上 offset,服务端从该位置继续推送缓冲区中的内容。
python
# 服务端:LLM Gateway 的 SSE 处理(FastAPI 示例)
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import asyncio
import json
app = FastAPI()
# 内存中缓存每个 stream 的已生成 chunks
# 生产环境建议用 Redis Stream 或 Kafka
stream_buffers: dict[str, list[dict]] = {}
@app.post("/v1/chat/completions/stream")
async def stream_chat(request: Request):
body = await request.json()
stream_id = body.get("stream_id") or generate_stream_id()
resume_offset = int(request.headers.get("X-Resume-Offset", "0"))
# 检查是否有可恢复的 stream
if stream_id in stream_buffers and resume_offset > 0:
buffer = stream_buffers[stream_id]
# 先推送已缓存但未送达的 chunks
async def resume_and_continue():
for i in range(resume_offset, len(buffer)):
yield f"data: {json.dumps(buffer[i])}\n\n"
# 然后继续生成新内容
async for chunk in generate_remaining(stream_id, body):
buffer.append(chunk)
yield f"data: {json.dumps(chunk)}\n\n"
return StreamingResponse(
resume_and_continue(),
media_type="text/event-stream",
headers={"X-Stream-Id": stream_id}
)
# 新请求
stream_buffers[stream_id] = []
async def generate():
async for chunk in llm_generate(body):
stream_buffers[stream_id].append(chunk)
yield f"data: {json.dumps(chunk)}\n\n"
return StreamingResponse(
generate(),
media_type="text/event-stream",
headers={"X-Stream-Id": stream_id}
)
python
# 客户端:带 resume offset 的流式消费
import httpx
def stream_with_resume(endpoint: str, body: dict, stream_id: str = None):
"""带 resume offset 的客户端"""
headers = {}
offset = 0
collected = []
if stream_id:
headers["X-Stream-Id"] = stream_id
while True:
headers["X-Resume-Offset"] = str(offset)
try:
with httpx.stream("POST", endpoint, json=body, headers=headers) as resp:
stream_id = resp.headers.get("X-Stream-Id", stream_id)
for line in resp.iter_lines():
if line.startswith("data: "):
chunk = json.loads(line[6:])
collected.append(chunk["content"])
offset += 1
# 正常结束
return "".join(collected)
except (httpx.ReadError, httpx.RemoteProtocolError):
# 断流了,带着 offset 重连
continue
优点: 真正的断点续传,不浪费 token,恢复延迟最低(只补发丢失的 chunks)。
局限: 需要自建 LLM Gateway;服务端需要维护 stream buffer,增加内存开销;不适合直接调用第三方大模型 API(你控制不了模型厂商的服务端)。
适用场景: 自建 LLM Gateway 的中大型团队,日均调用量超过 10 万次,需要精细控制 streaming 行为。
策略 4:Semantic Resume(应用层续写)
这是我们在 AI 写作平台中用得最多的方案。核心思路:不依赖底层协议,而是在应用层把「断流恢复」转化为「续写任务」。已生成的内容作为上下文,加上一个续写 prompt,让模型从语义层面接续。
python
from typing import Optional
from dataclasses import dataclass
@dataclass
class StreamState:
"""流式生成的状态快照"""
content: str
token_count: int
is_complete: bool
finish_reason: Optional[str]
metadata: dict
class SemanticResumeHandler:
"""语义续写处理器"""
# 续写 prompt 模板------这是关键
CONTINUE_PROMPTS = {
"narrative": "以下是已生成的内容。请从最后一句话的语义接续,不要重复已有内容,不要添加过渡句:\n\n{content}\n\n[续写]",
"code": "以下是已生成的代码。请从最后一行继续,保持相同的代码风格和缩进:\n\n{content}\n\n[继续]",
"technical": "以下是已生成的技术文章。请从最后一段的论点继续展开,保持相同的技术深度和写作风格:\n\n{content}\n\n[继续]",
}
def __init__(self, client, model: str = "deepseek-chat"):
self.client = client
self.model = model
def resume(
self,
original_messages: list,
partial_state: StreamState,
content_type: str = "technical",
max_resume_attempts: int = 2
) -> str:
"""从断点语义续写"""
if partial_state.is_complete:
return partial_state.content
# 构建续写上下文
# 取最后 2000 字符作为续写参考(避免 token 超限)
tail = partial_state.content[-2000:]
continue_prompt = self.CONTINUE_PROMPTS[content_type].format(content=tail)
resume_messages = original_messages + [
{"role": "assistant", "content": partial_state.content},
{"role": "user", "content": continue_prompt}
]
for attempt in range(max_resume_attempts):
try:
response = self.client.chat.completions.create(
model=self.model,
messages=resume_messages,
stream=True,
# 续写通常不需要太长的输出
max_tokens=4096,
)
continuation = []
for chunk in response:
delta = chunk.choices[0].delta.content
if delta:
continuation.append(delta)
continuation_text = "".join(continuation)
# 缝合:找到续写内容和原文的重叠部分,去重
stitched = self._stitch(partial_state.content, continuation_text)
return stitched
except (ConnectionError, TimeoutError):
if attempt == max_resume_attempts - 1:
# 续写也失败了,返回已有的部分内容
return partial_state.content + "\n\n[生成中断,部分内容可能不完整]"
return partial_state.content
def _stitch(self, original: str, continuation: str) -> str:
"""智能缝合:找到续写内容和原文尾部的重叠,去重"""
# 取原文最后 100 个字符,在续写内容中查找匹配
tail_len = min(100, len(original))
tail = original[-tail_len:]
# 在续写内容的前 200 个字符中查找重叠
search_window = continuation[:200]
best_overlap = 0
for i in range(len(tail) - 10, 0, -1):
suffix = tail[i:]
if suffix in search_window:
best_overlap = len(suffix)
break
if best_overlap > 0:
# 去除重叠部分
clean_continuation = continuation[best_overlap:]
return original + clean_continuation
else:
# 没有找到重叠,直接拼接
return original + continuation
关键设计点:
- 续写 Prompt 设计: 不同类型的文本需要不同的续写策略。叙事文本需要语义接续,代码需要保持风格一致,技术文章需要保持论点连贯。
- 缝合算法: 模型可能重复生成原文尾部的内容,需要一个去重逻辑。上面的
_stitch方法用滑动窗口查找重叠。 - 续写长度控制: 续写不需要再生成完整的剩余内容,通常补上缺失的部分就够了。
优点: 不依赖任何底层协议改动,纯应用层实现,适用于任何大模型 API。对于长文本生成,续写质量可以接近无缝。
局限: 续写质量依赖 prompt 工程;多轮续写可能累积风格偏移;每次续写都是一次新的 API 调用,有额外成本。
适用场景: 长文本生成(文章、小说、报告)、AI 写作平台、需要高内容完整性的场景。
策略 5:Dual-Stream Redundancy(双链路冗余)
这是成本最高但可靠性也最高的方案。核心思路:同时向两个模型(或同一个模型的两个实例)发起流式请求,主链路断开时自动切换到备用链路。
python
import asyncio
from typing import AsyncIterator
class DualStreamManager:
"""双链路流式管理器"""
def __init__(
self,
primary_client,
fallback_client,
primary_model: str = "deepseek-chat",
fallback_model: str = "qwen-max"
):
self.primary = primary_client
self.fallback = fallback_client
self.primary_model = primary_model
self.fallback_model = fallback_model
async def stream_with_fallback(
self,
messages: list,
switch_threshold: int = 30 # 前 N 个 chunk 内断流才切换
) -> AsyncIterator[str]:
"""
双链路策略:
- 主链路正常时使用主链路
- 主链路在前 N 个 chunk 内断流,切换到备用链路(因为此时浪费的 token 少)
- 主链路在 N 个 chunk 后断流,使用 Semantic Resume(因为切换成本太高)
"""
primary_stream = await self._start_stream(
self.primary, self.primary_model, messages
)
chunk_count = 0
collected = []
try:
async for chunk in primary_stream:
chunk_count += 1
collected.append(chunk)
yield chunk
except StreamInterruptedError:
if chunk_count < switch_threshold:
# 早期断流,直接切换备用链路从头开始
fallback_stream = await self._start_stream(
self.fallback, self.fallback_model, messages
)
async for chunk in fallback_stream:
yield chunk
else:
# 已经生成了大量内容,用 Semantic Resume 续写
partial = "".join(collected)
resume_handler = SemanticResumeHandler(self.fallback, self.fallback_model)
state = StreamState(
content=partial,
token_count=chunk_count,
is_complete=False,
finish_reason=None,
metadata={}
)
remaining = resume_handler.resume(messages, state)
yield remaining
async def _start_stream(self, client, model, messages):
"""启动一个流式请求"""
response = await client.chat.completions.create(
model=model,
messages=messages,
stream=True,
)
async def iterate():
async for chunk in response:
delta = chunk.choices[0].delta.content
if delta:
yield delta
return iterate()
优点: 最高的可用性和最快的恢复速度。对于关键业务(如实时客服、金融报告生成),双链路可以保障 SLA。
局限: 成本翻倍(同时调用两个模型);两个模型的输出风格可能不一致;实现复杂度高。
适用场景: 高 SLA 要求的 B2B 场景、实时交互场景、断流不可接受的关键业务。
三、策略对比与选型决策
5 种策略的对比
| 策略 | 实现复杂度 | 恢复延迟 | Token 浪费 | 额外成本 | 适用场景 |
|---|---|---|---|---|---|
| Transport Retry | ★☆☆ | 高(重新生成) | 高 | 无 | 短响应,聊天机器人 |
| Chunk Checkpoint | ★★☆ | 低(2-5s) | 低(checkpoint 间隔内) | Redis | 大多数流式应用 |
| SSE Resume Offset | ★★★ | 最低(<1s) | 零 | 自建 Gateway | 自建 LLM Gateway |
| Semantic Resume | ★★☆ | 中(3-8s) | 低 | 续写 API 调用 | 长文本生成 |
| Dual-Stream | ★★★★ | 最低(<1s) | 零或低 | 翻倍 API 成本 | 高 SLA 关键业务 |
选型决策树
vbnet
你的流式响应通常超过 2000 token 吗?
├── 否 → Transport-Level Retry 足够
└── 是
├── 你有自建的 LLM Gateway 吗?
│ ├── 是 → SSE Resume Offset(最优解)
│ └── 否
│ ├── 你的场景是长文本生成(文章/小说/报告)吗?
│ │ ├── 是 → Semantic Resume(续写质量最好)
│ │ └── 否 → Chunk-Level Checkpoint(通用方案)
│ └── 你的业务 SLA 要求 99.9%+ 可用性吗?
│ ├── 是 → Dual-Stream + Chunk Checkpoint 组合
│ └── 否 → Chunk-Level Checkpoint
实际生产中的组合使用: 我们团队的默认方案是 Chunk Checkpoint + Semantic Resume 的组合。正常情况下用 checkpoint 恢复,如果 checkpoint 恢复后内容质量不达标(通过一个简单的 coherence score 检测),自动降级到 semantic resume。
四、生产落地 Checklist
把断流恢复推上生产,除了选对策略,还需要关注这些工程细节。
4.1 监控指标
断流恢复系统的核心监控指标:
python
# Prometheus 指标定义
from prometheus_client import Counter, Histogram, Gauge
stream_total = Counter(
"llm_stream_total",
"Total streaming requests",
["model", "endpoint"]
)
stream_interrupted = Counter(
"llm_stream_interrupted_total",
"Interrupted streaming requests",
["model", "endpoint", "interrupt_type"]
)
stream_resume_success = Counter(
"llm_stream_resume_success_total",
"Successful stream resumptions",
["model", "strategy"]
)
stream_resume_latency = Histogram(
"llm_stream_resume_latency_seconds",
"Time to resume after stream interruption",
["model", "strategy"],
buckets=[0.5, 1, 2, 5, 10, 30]
)
stream_tokens_wasted = Counter(
"llm_stream_tokens_wasted_total",
"Tokens generated but discarded due to stream failure",
["model", "endpoint"]
)
stream_checkpoint_lag = Gauge(
"llm_stream_checkpoint_lag_chunks",
"Number of chunks since last checkpoint",
["session_id"]
)
关键告警规则:
stream_interrupted_total / stream_total > 5%:断流率超过 5%,检查网络或 provider 状态stream_resume_success_total / stream_interrupted_total < 90%:恢复成功率低于 90%,检查恢复逻辑stream_tokens_wasted_total突增:大量 token 浪费,可能是 checkpoint 频率太低
4.2 降级策略
当断流恢复系统本身出问题时的降级链:
vbnet
正常模式:Chunk Checkpoint + Semantic Resume
↓ (Redis 不可用)
降级模式 1:Transport Retry + 保留 partial response
↓ (重试 3 次仍失败)
降级模式 2:返回 partial response + 标记「内容不完整」
↓ (partial response < 100 token)
降级模式 3:切换到非流式同步调用(牺牲体验换可靠性)
4.3 Checkpoint 容量规划
Checkpoint 存储的容量估算:
diff
存储量 = 并发流数 × 平均 checkpoint 大小 × TTL
假设:
- 并发流数:1000
- 平均每个 checkpoint:50KB(约 2000 个中文字符)
- TTL:1 小时
存储量 = 1000 × 50KB = 50MB(Redis 内存占用可忽略)
但如果 TTL 设太长或忘记清理:
- 1000 并发 × 50KB × 24 小时 = 1.2GB/天
- 一周不清理就是 8.4GB
所以:TTL 一定要设,清理任务一定要有。
4.4 客户端 SDK 封装建议
如果你在封装面向业务的 SDK,建议把这些策略内聚到 SDK 层,让业务代码无感知:
python
class ResilientLLMClient:
"""对业务层透明的容错 LLM 客户端"""
def __init__(
self,
api_client,
checkpoint_store: CheckpointStore,
strategy: str = "checkpoint", # checkpoint | semantic | auto
content_type: str = "technical"
):
self.api = api_client
self.store = checkpoint_store
self.strategy = strategy
self.content_type = content_type
def stream(self, messages: list, session_id: str, **kwargs) -> Iterator[str]:
"""
业务层只需要调用 stream(),断流恢复逻辑完全内聚。
用法:
client = ResilientLLMClient(api_client, redis_store)
for chunk in client.stream(messages, session_id="user-123-task-456"):
process(chunk)
"""
# 自动选择策略
if self.strategy == "auto":
# 根据预估输出长度选择策略
estimated_tokens = self._estimate_output_length(messages)
if estimated_tokens > 10000:
strategy = "checkpoint+semantic"
elif estimated_tokens > 2000:
strategy = "checkpoint"
else:
strategy = "retry"
else:
strategy = self.strategy
# 分发到具体实现
if strategy == "checkpoint+semantic":
yield from self._stream_with_checkpoint_and_semantic(
messages, session_id, **kwargs
)
elif strategy == "checkpoint":
yield from self._stream_with_checkpoint(
messages, session_id, **kwargs
)
else:
yield from self._stream_with_retry(messages, **kwargs)
def _estimate_output_length(self, messages: list) -> int:
"""粗略估计输出长度,用于策略选择"""
# 简单启发式:根据 user message 中的关键词判断
last_message = messages[-1]["content"].lower()
if any(kw in last_message for kw in ["长篇", "详细", "完整", "全文", "小说"]):
return 20000
elif any(kw in last_message for kw in ["总结", "简短", "一句话"]):
return 500
return 2000
五、写在最后
LLM streaming 断流恢复不是一个「有就行」的锦上添花功能,而是长文本生成场景下的必备工程能力。当你的用户等了 3 分钟看到 80% 进度条消失时,他们的耐心不会给你第二次机会。
回顾一下核心策略:
- 短响应(<2000 token):Transport Retry 足够,别过度工程化
- 大多数场景:Chunk Checkpoint 是投入产出比最高的方案,半天就能接入
- 长文本生成:Semantic Resume 能提供最好的续写质量,但需要调 prompt
- 自建 Gateway:SSE Resume Offset 是最优解,但需要服务端改造
- 关键业务:Dual-Stream 保障 SLA,但要做好成本翻倍的预算
最后一条建议:别等到线上出了断流事故才开始想这个问题。今天就在你的 streaming 调用外面包一层 checkpoint,10 行代码的事。