跳到主要内容

对话式 REST:当你的聊天 UI 需要分页、过滤和排序时

· 阅读需 12 分钟
Tian Pan
Software Engineer

一名用户向你的购物智能体询问“150 美元以下、足弓支撑良好的跑鞋”。智能体尽职地返回了 12 个选项,但它们表现为单个聊天气泡中一长串超出视口的子弹点文本。用户滚动屏幕,找不着看到哪了,然后输入“只显示 Asics”——此时,你的智能体重新运行了整个搜索,而不是过滤它已经拥有的结果集。三轮对话后,用户正在通过一次一个提示词来发明一种查询语言,而你的产品感觉就像一个披着聊天气泡外壳的命令行。

这是我不断看到团队在交付时陷入的失败模式。他们在用户实际上想要的分面搜索(faceted-search)产品之上构建了一个聊天产品。模型没问题。检索也没问题。问题出在 UI 上,它的形态不适合这项任务。

我能给出的最简短的结论是:聊天是一种输入模态,而不是输出模态。智能体的职责是将用户意图转化为结构化查询。一旦结果集超过三项,正确的做法是渲染 UI,而不是继续说话。

聊天擅长处理模糊的单次交互,而拙于处理其他一切

当用户还不知道如何表述问题时,聊天功能大放异彩。“我想要一本像《Cryptonomicon》那样,但篇幅更短且没那么暴力的书”,这类查询是分面搜索框永远无法准确处理的,因为用户还在与自己的品味进行博弈。模型可以消除歧义,此时一段自然语言回复就是正确的输出形态。

但用户在生产环境中实际上执行的任务并不是模糊的单次交互。它们是人们在软件中一直做的事情:并排比较五个备选方案,将长列表过滤到剩下三个,按新鲜度、价格或相关性排序,以及翻阅无法在屏幕上完全显示的搜索结果。这些都不是对话式任务。它们是空间任务。用户需要的是视口(viewport),而不是对话记录(transcript)。

当智能体在单个消息中返回 12 个搜索结果时,用户必须在脑中完成空间化工作:将这些项目保留在工作记忆中,在心理上标记想要保留的项目,并记住智能体已经提到了哪些,以免重复询问。聊天日志本应是一份列表,现在却变成了一场对话的笔录。每一次额外的交互都会让情况变得更糟——通过重新提示(re-prompt)进行分页是我见过的最常见的聊天反模式(anti-pattern),它累积上下文的速度超过了任何实际工作流所能承受的范围。

Baymard Institute 的电子商务过滤器研究十年来一直在用不同的词汇强调这一点:当用户面对超过 7 项左右的结果集时,他们在寻求“更多结果”之前,会先尝试使用过滤和排序。这种模式并不会因为搜索框变成了 LLM 而改变。用户还是同样的人;任务还是同样的比较。改变的是我们不再为他们提供控制控件。

“给我看下 10 个”的拙劣模仿

第二种失败模式更为隐蔽。团队注意到单个聊天气泡无法承载结果集,因此他们推出了分页功能——但他们将其作为一种对话动词来推出。用户输入“给我看下 10 个”,智能体顺从地发送下一页作为另一个巨大的气泡。接着是“现在按价格排序”,然后是“实际上,隐藏 200 美元以上的”。

每一次这类交互都是用户在通过一次一个提示词来发明一种查询语言。产品团队为拥有一个“自然”的界面而自鸣得意,但他们交付的是一个更糟糕的 SQL 提示符版本:没有自动补全、没有模式提示(schema hints),且无法保证模型两次对“实际上,隐藏”的理解是一致的。现有的搜索产品都没有采用“以句子形式输入过滤器”的 UI,这是有原因的,并不是因为没人想到这一点。

更深层的问题在于,对话式分页丢弃了智能体已经拥有的结构化意图。模型已经知道候选集。它已经知道过滤维度——价格、品牌、颜色、尺寸、评分。它已经知道用户偏好的排序键。在每次细化(refinement)时重新询问模型,就像在每次切换列显示时重新运行 SQL 查询一样,是一个架构错误:这项工作属于客户端,针对结果集运行,而不是再去 LLM 那里跑一趟。

“结构化意图”的真实样貌

解决这一问题的架构转变描述起来很简单,但做起来影响深远:智能体输出一个结构化的搜索规范(specification),应用程序将该规范渲染为真实的 UI,而细化操作则针对渲染状态进行,而不是针对另一个模型调用。

在实践中,智能体针对搜索任务的输出不应该是 "这里有 12 款跑鞋:1. ... 2. ...",而更接近于:

  • 一个查询对象——解析后的意图:{ category: "running_shoes", price_max: 150, attributes: ["arch_support"] }
  • 一个结果集——一次性获取的候选项目
  • 一个分面模式(facet schema)——智能体认为对当前查询有用的过滤维度(品牌、尺码、重量、坡差、地形),并填充了数量
  • 一个默认排序——智能体对这里“好”的定义的最佳猜测(评分 × 价格,或纯相关性)

聊天线程会渲染一个带有正常过滤器标签(filter chips)和正常排序控件的标准产品列表,同时辅以智能体想要添加的任何对话式解释(“我优先推荐了足弓支撑评价高于平均水平的鞋款”)。当用户切换过滤器时,应用程序会更新渲染状态——这与常规电子商务应用运行的 JavaScript 相同。只有当用户在对话频道中真正说出新内容时,才会重新调用智能体:一个新的约束、一个澄清问题,或是在可见选项中请求推荐。

这就是过去一年中生成式 UI(generative-UI)工作——Google 的 A2UIAG-UIMCP-UI 以及 Vercel 的 AI SDK generative UI——所趋向的一致方向。尽管协议各异,但答案的形态是完全相同的:智能体输出结构化的 UI 规范,宿主环境渲染真实组件,模型不再负责布局。我想说的是,你不需要任何这些框架也能做到这一点——你需要停止将模型的回复视为展示层,而开始将其视为规范。

分面搜索是这里遗失的先祖

团队不断意外地重新发明这一模式,即分面搜索(Faceted search),讽刺的是:电商行业在 2000 年代初期就完美解决了这个问题。分面是根据结果集动态生成的过滤器。计数、范围、切换开关、顶部的激活过滤标签、一键移除。整套设计语言早已存在,在你用过的每个零售网站上都有现成的案例,而现在它正被人们在聊天线程中默默地、笨拙地重新推导。

2026 年出现的混合模式非常有趣。不是“自然语言分面”,正确的模型是“自然语言填充分面”。Agent 读取用户的散文式查询,运行混合检索——向量用于语义意图,关键词用于精确匹配,结构化用于可过滤属性——并呈现出查询真正具有差异的分面。搜索语料库中的“轻量化越野跑鞋”,分面就是重量、落差、齿深、品牌。搜索“100 美元以下的办公运动鞋”,分面就是颜色、品牌、皮质/合成材质、价格区间。Agent 不是从静态分面列表中挑选,而是为每个查询生成相关的轴。

如果做得好,它消除了“用户输入自然语言”和“用户点击过滤器”之间的虚假选择。两者都在同一个查询、同一个 UI 中发生。Agent 将松散的意图转化为严谨的查询,并为用户提供微调的控制权。Elastic 最近在 AI 辅助分面生成方面的工作以及 Google 的分面向量混合检索研究是权威参考,但这种模式比 LLM 时代更久远——它只是被那些意识到自己的聊天记录正在溢出的团队重新引入到了 Agent 产品中。

判定聊天何时应该停止说话的决策规则

我在团队审计自己的 Agent UI 时给出的启发式规则非常简短:

  • 如果答案是一个句子,就以句子形式渲染。 摘要、解释、小范围内的推荐、带理由的“是/否”。聊天是合适的界面。
  • 如果答案是一个条目,就以卡片形式渲染。 卡片包含元数据、操作,以及下方用于后续交流的聊天槽位。不要将结构化数据埋没在散文中。
  • 如果答案是两到三个条目,就以小型对比视图渲染。 并排显示,可见字段相同,标出 Agent 推荐的选择。在工作记忆中这仍是可处理的。
  • 如果答案是四个或更多条目,就渲染带有过滤器和排序的列表。 Agent 输出查询和分面 Schema;应用渲染结果集;针对渲染状态进行微调。
  • 如果答案是一个流程——多步决策、工作流、交易——就渲染工作流。 Agent 不应该通过聊天轮次来引导结账。Agent 应该生成购物车,让应用的结账 UI 接管。

“3”这个数字并非魔法。它大概是大多数用户无法再在工作记忆中保留选项集,并开始想要对比的临界点。根据你的留存数据选择你自己的阈值,但一定要选定一个——我看到的失败模式是阈值默认为无限大,导致聊天线程变成了模型可以序列化的任何结果集的垃圾堆放场。

为什么团队会抵制这样做

两个原因,都很糟糕。

第一个原因是聊天的交付成本低。单个文本气泡可以在任何设备上运行,不需要设计规范,不需要组件库,也不需要任何人争论该显示哪些字段。那些停留在“模型返回了一个列表,我们把列表渲染成一段话”的团队做的是速度选择,而不是 UX 选择。这种代价以后会体现在用户真正想完成的工作流留存率低下。

第二个原因是聊天看起来像是具有差异化的东西。产品团队担心,如果结果集被渲染成带有正常过滤器的普通电商列表,用户就不会意识到其中有 AI 参与。这是错误的担忧。用户是为了任务而来,而不是为了技术。聊天界面应该是输入端——Agent 在这里帮助用户构建查询、消除意图歧义并解释权衡——而结构化界面应该是输出端,即实际工作发生的地方。“AI 感”体现在查询的质量和分面的相关性上,而不是在聊天气泡中。

应该监测什么

如果你想发现你的产品是否陷入了这个陷阱,最廉价的诊断方法是查看会话追踪(session traces),并计算每个会话的:

  • 单个 Agent 回复中最大条目数的中位数
  • 看起来像手动过滤(“只显示 X”、“不要 Y”、“按 Z 排序”)的后续轮次频率
  • 在第三次此类过滤轮次后的流失率
  • 从第一次查询到用户点击最终条目的时间

如果你最大的回复超过五个条目,且你的“通过对话进行过滤”的比例不低,那么你交付的是“对话式 REST”,而你的留存率正为此付出代价。修复方法不是换一个更好的模型,而是一个知道何时停止聊天的 UI。

落地的认知

Agent 的任务是将用户意图转化为结构化查询 —— 而不是充当整个界面。一旦你接受了这一点,架构也就顺理成章了:模型输出查询和结果集,应用程序将该结果集渲染为真实的 UI,而对话线程变成了用户协商意图的地方,而不是用户进行空间化操作的地方。

2026 年那些做对方案的团队并不是在交付更漂亮的聊天气泡。他们交付的产品中,聊天是一个频道,而渲染的工作区是另一个,Agent 同时填充这两者,且用户永远不需要说“给我看下 10 条”。他们只需要滚动即可。

References:Let's stay in touch and Follow me for more thoughts and updates