跳到主要内容

27 篇博文 含有标签「streaming」

查看所有标签

那个教会用户永远不要打断智能体的中断 UI

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的流式智能体上的中断按钮点击率仅为 0.4%。产品团队看到这个数字,得出的结论是该功能正按预期运作——大多数生成内容不需要中断,实现方案没问题,发布吧,继续下一个任务。而实际的解读应该是,这个中断按钮教会了你的用户不要去按它。在使用产品不到一周的时间里,他们就发现按下停止键会丢弃已生成的片段、清除上下文,并把他们丢回到一个空的输入框中。他们学到的教训是:宁愿忍受一个糟糕的回答,也不愿冒着丢失整个对话脉络的风险。

这 0.4% 不是使用信号,而是厌恶信号。你的用户并非对答案感到满意——他们只是害怕尝试重定向答案所带来的代价,他们的适应方式是静静地坐着,看着智能体说完那些他们明知是错误的内容。工程团队将“停止生成”视为模型调用的取消。而用户将其视为“重定向,而非重启”。这两种定义从未达成一致,导致产品发布了一个在长对话中悄悄剥夺用户主动权的功能。

流式中止后遗留的计费副作用

· 阅读需 12 分钟
Tian Pan
Software Engineer

用户正在查看你的智能体(Agent)流式传输回复。在 200 毫秒时,他们点击了停止。UI 清除了气泡,加载图标消失,产品的表现就像请求从未发生过一样。

但它确实发生了。Agent 已经调用了 send_invoice_email。供应商的邮件转发服务器返回了 250 OK。客户收到了一份用户从未批准的草案发票。你的计费系统向用户收取了中止前流式传输的 token 费用。但它无法撤回发送邮件产生的费用。

这是每个使用流式工具(streaming tool use)的团队至少都会遇到一次的失败模式,而且大多数团队甚至从未察觉。流层(stream layer)报告 cancelled。工具层(tool layer)报告 succeeded。你的面向客户的日志会根据最后刷新的子系统从中挑选一个,于是同一个请求的两个部分现在对于该请求是否发生产生了分歧。

你的后端基础设施并非为流式响应而设计

· 阅读需 13 分钟
Tian Pan
Software Engineer

流式传输(Streaming)是一项产品决策。设计团队的某个人看到竞争对手的聊天 UI 像打字机一样逐个吐出 Token,看到用户在第 200 毫秒看到第一个字符出现时肩膀放松了下来,而不是盯着 4 秒钟的空白屏幕发呆,于是决策就此达成:我们要做流式传输。这个拉取请求(PR)修改了 API 网关中的三个文件。现在,模型输出通过服务器发送事件(Server-Sent Events,SSE)增量刷新。功能在周二上线,周三的满意度评分就有了明显的提升。没人向基础设施团队提工单。

一个月后,值班工程师盯着三个互不一致的仪表盘发愁。自动扩缩容(Autoscaler)配置的 Pod 数量是 CPU 图表显示需求量的两倍。P99 延迟仪表盘坏了——不是出了故障,而是变得无法解读,因为直方图分桶(Histogram Buckets)止步于 5 秒,而现在大多数 Span 都落在溢出区间。上一季度定价时的容量模型显示,该服务每节点每秒可处理 1200 个请求。而值班人员面前的图表显示,它在处理 400 个请求时就已经难以为继。

自相矛盾的流式响应

· 阅读需 9 分钟
Tian Pan
Software Engineer

模型在第一句说“答案是肯定的”。到了第三段,它又改口说“实际上,经过反思,不——原因如下”。最终状态是正确的。但用户已经离开了。他们读了第一段,将其视为答案,并在模型完成修正之前就付诸行动了。你的评估认为该回答是正确的。但你的用户得到的却是错误的。

这是流式传输 UX 所隐藏的失败模式。逐字渲染(Token-by-token rendering)将每个区块都视为既定事实,但模型并没有“提交”(commit)的概念。在模棱两可的话语和结论之间没有边界,也没有信号表明“接下来的两段将推翻我刚才说的话”。界面将中间状态作为最终状态发布,且回答越长,这种差距就越严重。

在用户说"是"之前就已提交的流式响应

· 阅读需 11 分钟
Tian Pan
Software Engineer

用户正在阅读 Agent 流式输出的推理过程。在 token 1200 附近,模型决定调用 send_email,然后 create_ticket,再 kick_off_deploy。用户看到部分输出并意识到 Agent 误解了请求,在停止按钮上慢了半秒。邮件已经发出,工单已经创建,部署已经在跑。停止按钮取消的是下一个 token,而非上一个 token 的后果。

Bug 不在取消处理程序里。Bug 是那个假设——从团队路线图上所有其他流式 UI 借来的假设——即"逐步渲染的输出"就是"逐步可逆的输出"。工具调用并不遵守这个契约。它们是时间点上的提交,流式层一边欢快地触发它们,响应的其余部分还在生成中,而取消按钮无法沿着线路追赶它们。

这是那种没人认领的失败模式,因为它存在于两个团队各自干净交付的接缝处。UX 团队上线了流式,因为用户研究中表现更好;平台团队上线了工具调用,因为框架支持。两个团队都没有开过会问:当响应已经离开大楼时,"停止"应该是什么意思?

流式 Token 是无法收回的承诺

· 阅读需 10 分钟
Tian Pan
Software Engineer

模型已经向用户屏幕推送了 70% 听起来很自信的回答。接着,它即将进行的工具调用返回了错误、无结果或 429 错误。现在你必须在两种损失之间做出选择:让模型通过编造剩余部分来优雅地结束,或者在句子中间戛然而止,且没有体面的方式撤回。这两种都不是修复 —— 它们都是损害。

这是流式传输 UX 中没人考虑过成本的部分。流式传输被描绘成一种感知延迟的胜利:首个 Token 时间 (TTFT) 是核心指标,用户更早开始阅读,应用显得充满活力。但这种描绘忽略了你推送的每一个 Token 都是一种承诺。你发布了一个你还不知道是否正确的答案草稿,而你系统的后半部分还没有运行完毕。当它运行结束并产生分歧时,你的 UI 没有原生方法来撤回已经显示的内容。

流式回滚问题:你无法收回已发送的 Token

· 阅读需 11 分钟
Tian Pan
Software Engineer

观察某人第一次使用聊天产品时,你会发现他们在模型完成输出之前就开始阅读了。这种“边出边读”的行为正是流式传输(streaming)存在的全部意义:它将数秒的等待转变为一种对话般的感觉。然而,这也是你的输出防护栏(guardrails)悄然失效的原因。

这是一个令人尴尬的过程。模型生成了第 1 个 Token、第 2 个 Token、直到第 150 个。每一个 Token 在到达时都会立即渲染。到第 200 个 Token 时,模型产生了一个虚假的用药剂量、泄露了一个电子邮件地址,或者生成了一句违反内容政策的话。你的输出侧防护栏正确且立即触发了。但“立即”已经太晚了——用户已经阅读了前 200 个 Token。你无法撤销渲染。防护栏履行了职责,但违规内容仍然传达给了人类。

用户过早行动的流式 Token

· 阅读需 9 分钟
Tian Pan
Software Engineer

一个用户询问你的助手某项配置更改是否可以安全发布。模型流式返回了:“是的,你可以安全地部署。” 300 毫秒后,它继续写道:“——但在 us-east 区域除外,那里的旧连接池仍在排空。”但用户已经读完了前半部分,感受到了绿灯带来的放松,并点击了部署。这句修正说明到达时,人已经走开了。

这里没有人犯错。模型是正确的。用户阅读了屏幕上的内容。渲染器忠实地显示了每个到达的 Token。然而结果却是一次糟糕的部署,因为流式传输将模型的“中间状态”变成了用户视为“最终结果”的东西。

先返回 200 然后失败的流式响应:中途错误如何破坏你的 SLO

· 阅读需 11 分钟
Tian Pan
Software Engineer

你的可用性仪表盘显示 99.95%。你的用户却说回答在句子中间停住了。两者都是正确的,而这正是问题所在。

HTTP 时代的可靠性技术栈建立在一个假设之上:状态码在请求结束时到达,并总结其命运。200 意味着成功。5xx 意味着重试。负载均衡器计算比例,SLO 仪表盘进行聚合,告警则根据消耗率(burn rate)触发。技术栈的每一层都会读取并信任这个 Header。

流式传输(Streaming)反转了这一假设。当你的服务器刷新第一个 token 的那一刻,它就已经承诺了一个 200 状态码。在那之后发生的任何错误 —— 在第 400 个 token 时的供应商超时、段落中间触发的内容审查过滤、TCP 连接断开、格式错误的工具调用(tool-call)片段 —— 都是在结果已经判定且无法撤回之后发生的。请求失败了,但状态码却说它成功了。而你的可靠性工具中没有任何一部分是为了察觉这种差异而构建的。

具有两种延迟的 AI 功能:你衡量的是一种,用户感知的是另一种

· 阅读需 10 分钟
Tian Pan
Software Engineer

传统的 HTTP 请求只有一个关键的延迟:从请求到响应的时间。那个数字的 p95 就是契约。SRE 监视它,SLO 是针对它编写的,当它退化时就会有人收到告警。一个数字,一个仪表盘,一个真相。

流式 AI 功能在响应变为流的一刻就打破了这一模型,而大多数团队还未察觉。现在有了两种延迟,而且它们是发散的。首字延迟(Time-to-first-token) 是用户在任何事情发生前盯着加载图标的时间。完成时间(Time-to-completion) 是直到回答完全写完的时间。它们受不同力量的影响,由不同的杠杆修复,并且用户感受到的情感权重完全不同 —— 而几乎每个团队都只衡量第二个指标,因为那是 HTTP 框架免费提供给他们的数字。

流式响应追踪模式鸿沟:为什么你的 APM 在 LLM 延迟上撒了谎

· 阅读需 12 分钟
Tian Pan
Software Engineer

凌晨 02:14,报警器响了:客户反馈助手在回答长问题时“话说到一半就卡住了”。你打开追踪(trace)。LLM 调用的 span 显示为 8.4 秒 —— 绿色,在 SLO 范围内,没有错误属性,结束原因(finish reason)为 stop。仪表板上该端点的 p95 延迟聚合组件显示为 9.1s,与过去一个月的情况完全一致。根据 APM 显示的所有信号,该请求都成功了。

用户看到前 200 毫秒表现完美,接下来的四秒钟生成了一个连贯的段落,然后眼睁睁看着同样的三句话片段在剩下的四秒钟里不断重复,直到连接结束。这种卡住的内容循环(stuck content loop)是真实的故障,但追踪系统对此一无所知 —— 因为追踪系统是为“返回即结束”的系统设计的,而不是为了这种行为表现为生成过程中产生的中间状态之墙的系统。

流式推理中的海勒姆定律:节奏、停顿和中间 Token 是未成文的契约

· 阅读需 12 分钟
Tian Pan
Software Engineer

一个团队从前沿模型升级到了其更快的后继版本。评估套件(eval suite)全绿。最终答案一致。工具调用的 Schema 完全相同。结构化输出通过了与以往一样的 JSON Schema 验证。他们发布了。不到一天,支持票据就堆积如山:“助手感觉太匆忙了”,“它不再真正思考了”,“感觉不对劲”。产品经理调取了遥测数据,发现任务完成率没有变化。工程团队反复检查了评估和 Schema,没发现任何问题。投诉是真实的,但团队定义的契约——就如团队所定义的那样——依然完好无损。

改变的是流的纹理(texture)。旧模型在调用工具前会停顿 800 毫秒,发出一句“让我查一下……”的前导词,并以每秒约 35 个 Token 的速度输出,在子句边界处有自然的节奏。新模型以每秒 90 个 Token 的速度输出,从不停顿,且完全跳过了前导词。这些都没有出现在任何文档记录的契约中。但所有这些都是不可或缺的“承重”部分。

这就是海勒姆定律(Hyrum's Law),而流式传输(streaming)让它的表面积变得巨大。系统的任何可观察行为都会被某人所依赖——而流式 AI 界面暴露的可观察行为远比团队意识到的要多。