想象你走进一家新开的“机构图书馆特快代写服务”柜台:你报出一个问题,前台接待员(前端代码)先填一张查书工单,从书库里搬来一摞相关资料;后台的研究员(大模型)拿到资料当场写起报告,但不是一次性打印完,而是像传真机一样一段一段往外吐(流式生成);每吐出一页,前台就誊写成排版漂亮的版面(Markdown 渲染),在关键句子上标好 \[1\]\[2\] 角标(引用来源);你随时可以喊“停”(中断请求),也可以连着追问好几个问题(竞态处理)。 这套“图书馆特快代写”流水线,就是前端 RAG 架构。过去大家以为 RAG 只是后端的事:检索文档、拼 Prompt、调用大模型,前端随便放个文本框和输出区就行。但真正决定用户体验好坏的全在前端:首字出现快不快、文字是否逐字蹦出、Markdown 有没有排版、引用能不能点、中途停止是否真的停得下来。这篇文章就用“图书馆代写服务”这个比喻,带你从前端视角把 检索 API 调用 → 流式生成展示 → Markdown 实时渲染 → 引用来源展示 → 错误中断处理 → 性能优化 → 全链路体验设计这条流水线一次装好。
学会前端 RAG 全链路设计,你就能把“能用的 AI 页面”升级成“人人都爱用”的产品,这是 AI 应用前端工程师最值钱的核心竞争力。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
前端RAG全链路架构 Q: 什么是RAG?引用来源:[1]RAG论文 [2]向量数据库 [3]Prompt工程Q: 什么是RAG?Q: 什么是RAG?引用来源:[1]RAG论文 [2]向量数据库 [3]Prompt工程Q: 什么是RAG? Q: 什么是RAG?引用来源:[1]RAG论文 [2]向量数据库 [3]Prompt工程Q: 什么是RAG?Q: 什么是RAG?引用来源:[1]RAG论文 [2]向量数据库 [3]Prompt工程Q: 什么是RAG?
检索API调用
你走到图书馆代写柜台,问:“帮我找找前端 RAG 怎么做流式渲染?”前台不会自己冲进书架乱翻,而是先填一张查书工单:查什么主题、要几本书、限定哪个分类。这张工单放到前端,就是检索 API 的请求参数。工单写得好不好,直接决定研究员有没有好资料可用。
前端通常用 fetch 向后端检索接口发 POST 请求,请求体里包含三个核心参数:query(问题文本)、top_k(返回文档条数)、filters(限定某个知识库或文档集)。后端返回命中文档片段以及每条文档的召回相关度分数,前端再把这些片段塞给大模型做生成。参数设计的第一步就是把“用户的一句话”规范成后端约定的工单结构。
除了参数,体验问题大多出在两张工单同时到达:用户第 3 秒问“A“,第 5 秒追问”B“,第 6 秒 A 的检索结果先返回,把 B 的结果顶掉了。这就是竞态。解法是给每次请求一个自增序号,响应回来时只认最新序号——好比顾客手里只认最大的单号。同时还得做超时重试:连接 8 秒没响应,自动重试一次,让”取书失败”尽量少发生在用户面前。
检索相关度背后其实是一个相似度打分公式,前端通常用它来排序展示命中文档:
回到柜台场景:工单里“要几本书、限定哪个分类”对应 top_k 和 filter;而排序算法帮前台决定“这本放最上面”。前端把检索工单封装好,后面整条流水线才有了可靠的原料。
- 查询向量用户的提问被编码成一串数字,就像把“要什么主题的书”写进工单。
- 文档向量每份资料也会被编码成同样长度的数列,表示它“属于哪个主题区域”。
- 点积算相似分子把两组数字逐位相乘再累加,得到方向一致性。
- 归一化成余弦分母把向量长度差异抹平,让分数稳定在 0 到 1 之间,方便前端排序。
流式生成展示
研究员不是把整份报告打印完才递给你,而是写到一段就“唰”地吐出一页,前台一边接一边往你们面前的公告板上贴——这种逐字蹦出的效果,就是流式生成的魅力,也是 AI 问答界面和普通文本框最大的区别。
要让文字逐段到达前端,常用的协议是 SSE(Server-Sent Events),一条由服务端单向推送的通道。前端先通过 fetch 拿到响应体 ReadableStream,再用 reader.read() 循环读取二进制分块,解码成文本。每次拿到一小段,就把这段“增量”追加到显示缓冲区的末尾。
增量追加看起来简单,但有个坑:如果每个 token 直接拼 HTML,界面会闪成 PPT;正确做法是先累积文本缓冲区,再交给渲染层统一处理。加上 requestAnimationFrame 把同一帧内的多个分块合并刷新,公告板才能“一秒贴好几次但不显得忙乱”。
流式累积的过程可以写成:
公告板上的内容就是这样从空白变成完整报告的。首 token 时间决定了用户觉得“快不快”,而后续的增量渲染决定了“顺不顺”——这两件事,前端都牢牢握在手里。
- 初始状态还没收到任何内容时,展示区是空字符串。
- 增量分块网络每次到达的一小段文本记为 Δk,如同打印机吐出的单页。
- 追加合并新状态等于旧状态拼接上增量,这是打字机效果的核心。
- 收敛完整当 k 到达末尾 K,最终文本就是完整回答。
Markdown渲染
打印机吐出来的纸上全是记号:# 代表大标题、- 代表列表、`` 代表代码块、|` 代表表格。前台不能把这些记号原样贴出去,得先誊写成正式版面——这套誊写过程就是 Markdown 渲染。
前端常用的解析库有 markdown-it、marked 等,它们把 Markdown 源文本转成 HTML 标签;代码块交给 highlight.js 做语法高亮,数学公式交给 KaTeX 排版。难点在于流式场景:文本是不完整的,可能刚收到半个 ``` 或半个表格,渲染器直接解析会把界面闪成乱码。
业界常用做法是“渲染当前完整缓冲”:把未闭合的代码块临时补一个收尾符,或者先按普通文本展示,等语法块闭合后再升级为高亮代码块。另一个关键点是 XSS 防护——AI 生成内容或知识库里可能夹带恶意 HTML,渲染结果必须经过 sanitize(清洗)才能挂到页面上,否则等于把后门敞给用户。
从 Markdown 到 HTML 可以形式化为:
前台誊写时最怕“字没写完就急着排版”——所以前端要等一小段缓冲再渲染,同时对不完整的语法块“打圆场”。排版这件事,急不得,但也慢不得。
- 源文本AI 返回的 Markdown 是带格式记号的手稿。
- 转换函数解析器按语法把记号翻译成 HTML 标签的嵌套树。
- 增量渲染流式场景下,把当前完整缓冲交给 φ,得到可挂载的界面片段。
- 组合最终页多段结果依次合并,形成完整的聊天问答 DOM。
引用来源展示
特快代写服务的报告和普通回答最大的区别,就是每一句关键结论后面都跟着一个 \[1\]\[2\] 小角标,像报纸上“详见第三版”的小字。顾客点一下角标,就能跳到底部的参考文献架,看到书名、页码和摘要——这就是 RAG 最值钱的地方:有出处,才可信。
后端在生成时会把检索到的文档按顺序编号,在文本里插入 [n] 角标。前端拿到后要做的第一件事是解析角标:要么在流式追加时用正则在文本里匹配 [数字],要么后端直接给结构化字段。解析出来的角标需要构建一张“序号 → 来源条目”的映射表,才能支撑后面的点击。
映射表做好后,角标渲染成可点击的小按钮,点击就调 scrollIntoView({ behavior: 'smooth' }) 平滑滚动到对应的来源卡片;反过来,点击来源卡片也能高亮正文里引用它的所有角标——形成正文 ⇄ 来源双向联动。这样用户从“看到结论”到“核实出处”只需要一次点击。
序号与来源的映射可以写成一个函数:
顾客点角标、跳书架的体验,全靠这张映射表兜着。没有引用来源的 RAG 回答就像没有参考文献的论文,用户不敢信;有了双向联动,信任感立刻建立起来。
- 标注号生成时模型在关键句后输出 [1]、[2] 等角标。
- 映射数组后端返回的 citations 是按序号排好的来源列表。
- 跳转定位点击角标后,前端用元素坐标把视图滚到对应来源卡片。
- 条目绑定前端把 [i] 渲染成可点击角标按钮,绑定映射表中对应条目。
错误中断处理
顾客看到一半突然改主意:“别写了,我换一个问题。”前台必须立刻给研究室挥旗子,让打印机停下。这个“挥旗子”动作,在前端就是 AbortController——一个能对 fetch 发出中断信号的对象。
调用 controller.abort() 后,fetch 会抛出 AbortError,配合 try/catch/finally 就能在取消时清理界面状态:关掉打字机动画、丢弃还没处理完的残余分块、把按钮恢复成可发送状态。如果只做“前端不再显示新内容”而不传中断信号,后端还在白算白打,浪费算力,所以中断一定要从请求层真正切断。
除了主动中断,还要处理被动故障:网络闪断、服务端 5xx、连接超时。常见做法是设计一个状态机:idle → loading → streaming → done / error / aborted,每个状态对应一套 UI 文案和按钮;比如 15 秒没收到首个 token 就显示“生成超时,请重试”。
整个中断-恢复逻辑可以用一个状态转移描述:
前台接待员最重要的职业素养是“永远知道现在卡在哪一步、下一步怎么走”。状态机就是这种素养的代码化:无论用户喊停、断网还是超时,界面都不会变成一个死胡同。
- 状态变量用一个布尔信号标记这次请求是否已中断,相当于流程里挂一块“停止”牌。
- 触发中断用户点击停止,控制器发出中断事件,fetch 立刻抛 AbortError。
- 状态转移UI 根据信号切到 aborted,清理打字机计时器并丢弃残余分块。
- 兜底恢复异常路径也统一回到可重试的初始状态,保证界面永远有出口。
性能优化实践
公告板贴纸条,如果每来一个字就贴一次,顾客眼花,前台手酸。聪明的前台会“攒一攒”:每隔一小段时间才贴一次,一次贴一小撮字。这个“攒一攒”就是渲染节流(throttle)——固定时间窗口内最多渲染一次,把窗口内到达的多个 chunk 合并成一次 DOM 更新。
另一个常见手法是防抖(debounce):用户敲完字停住 300ms 后才发起检索请求,避免每敲一个字母就触发一张查书工单。两者的区别在于:节流是“按时放行”,防抖是“等安静了再放行”;节流适合高频文本刷新,防抖适合输入联想。
长聊天记录还可以用虚拟滚动只渲染可见区域;相同问题加缓存,命中缓存直接秒回上次结果;Markdown 解析结果以内容哈希为 key 缓存,重复文本直接复用 DOM,不再重新解析。在 React 里还能用 useDeferredValue 把低优先级渲染延后,保证输入框永远不卡。
节流的数学表达很简洁:
柜台贴纸条讲究“节奏感”,前端渲染也讲节奏。节流防抖不是炫技,而是让浏览器在“流畅”和“实时”之间找到那个刚刚好的平衡点。
- 时间差记录上次刷新的时刻,每次拿到新 chunk 先算时间差。
- 阈值判断只有超过时间窗 τ 才放行这次更新,否则把 chunk 丢进合并队列。
- 更新时间戳放行后把当前时刻记为最新时刻,下批 chunk 以它为基准重新计时。
- 效果渲染频率被封顶,主线程不再被高频文本刷新拖垮。
全链路体验设计
把前面所有柜台服务拼成一条传送带:顾客下单(查询)→ 自动查书(检索)→ 研究员写作(生成)→ 前台排版(渲染)→ 贴角标(引用)→ 顾客随时喊停(中断)。单看每一步都不难,难的是把它们装进一个可维护的生命周期里。
工程上通常把整条流水线封装成一个组合式 Hook 或服务:输入查询后自动执行“检索 → 流式生成 → 渲染 → 引用 → 可中断”的完整流程,对外暴露 data、loading、error、stop 等状态,界面组件只负责展示。这样业务代码不用关心细节,任何一步都能独立替换。
体验层面的加分项还包括:骨架屏(等待首 token 时显示占位行,缓解焦虑)、思考过程折叠展示(reasoning/thinking 内容可展开查看)、ErrorBoundary(渲染崩溃时兜底降级,而不是白屏)、以及端到端指标监控(首 token 时间、渲染字数/秒)。这套组合拳就是“前端 RAG 一体化方案”。
整条流水线可以用函数组合表达:
传送带最重要的不是某一段跑得快,而是节拍一致、衔接不丢。检索、生成、渲染、引用、中断、优化——当前端把这几件零件拧成一条流水线,用户感受到的就不再是一个个按钮,而是一种“要什么有什么”的顺滑体验。
- 查询输入用户在输入框中的问题 Q 是整个流水线的起点。
- 检索映射S(Q) 从知识库里选出最相关的 Top-k 文档集。
- 生成流水G 把文档集传给大模型,产出标记着引用的 Markdown 数据流。
- 渲染与交互组合函数从右到左执行,R 渲染 Markdown 为界面,C 处理角标点击、停止生成等交互。
AI 应用场景
智能客服工单助手
阶段:检索API调用 + 引用来源展示
解决的问题:传统客服需要人工翻工单库才能回答用户问题,回复慢且没有依据。接入前端 RAG 后,用户提问先走检索接口从历史工单库取回相似案例,再流式生成回答,末尾以工单号作为引用角标,让用户自己也能点击核实。
具体案例:电商大促期间用户问“我的订单发票怎么开?”前端调用检索接口从知识库取回 3 条流程文档,流式展示回答,末尾 [1][2] 角标分别指向“发票申请流程”和“常见驳回原因”两条工单页,点击即可查看原文。
企业Wiki问答助手
阶段:Markdown渲染 + 引用来源展示
解决的问题:企业文档散落在 Wiki 各个角落,新员工很难精准搜索。RAG 助手把 Wiki 文章切片后做向量检索,AI 回答用 Markdown 渲染成带标题、列表、表格的规范界面,引用直接跳转到 Wiki 原文段落,核实时不再需要二次搜索。
具体案例:新同事问“项目发布前要过哪些检查?”回答渲染出一份带复选框的发布检查清单,引用角标直达 Wiki 中“发布检查表”章节,点入即为原文,信任感立刻建立。
AI代码生成面板
阶段:流式生成展示 + 性能优化实践
解决的问题:IDE 插件里生成代码需要逐行“打字机”输出,同时不能阻塞编辑器的输入。前端用流式读取加节流渲染,把代码块交给语法高亮器着色,底部展示引用的开源实现文件路径,点击即可跳转查看原文。
具体案例:开发者在插件面板里输入“写一个防抖函数”,界面逐行打印代码、关键字高亮,并附上引用的开源实现片段路径;点击引用卡片后,编辑器自动定位到对应文件的行号。
Python 演示
import time DOCS = [ "RAG 的前端负责把用户问题发给后端检索接口.", "流式生成采用 SSE 协议, 前端逐块读取 token.", "Markdown 实时渲染需要先解析再增量挂载 DOM.", "引用来源展示通过 [序号] 角标关联来源列表.",] def simple_retrieve(query, docs, top_k=2): """模拟检索接口: 按字符命中数给文档打分并返回序号.""" scores = [] for idx, doc in enumerate(docs): hit = sum(1 for ch in query if ch in doc) scores.append((hit, idx)) scores.sort(key=lambda x: x[0], reverse=True) return [scores[i][1] for i in range(min(top_k, len(scores)))] def stream_generate(query, docs, hit_idx): """模拟大模型流式生成: 每次 yield 一小段文本.""" for j in hit_idx: yield f"[{j + 1}] 相关资料: {docs[j]}\n" time.sleep(0.05) yield "\n---\n" yield "> 结论: 前端 RAG 由检索, 流式生成, 渲染, 引用四段构成.\n" yield "[1] 检索接口封装\n[2] SSE 读取\n" def render_markdown(text): """模拟 Markdown 渲染: 把文本转成简易 HTML 结构.""" html = [] for line in text.splitlines(): line = line.strip() if not line: continue if line.startswith("---"): html.append("<hr>") elif line.startswith("> "): html.append(f"<blockquote>{line[2:]}</blockquote>") elif line.startswith("["): html.append(f'<span class="cite">{line}</span>') else: html.append(f"<p>{line}</p>") return "\n".join(html) def main(): query = "前端RAG怎么做流式渲染" idxs = simple_retrieve(query, DOCS, top_k=2) buffer = "" for chunk in stream_generate(query, DOCS, idxs): buffer += chunk print(render_markdown(buffer)) print("-" * 30) if __name__ == "__main__": main()
def simple_retrieve(query, docs, top_k=2):定义“查书工单”函数:按字符命中率给文档打分。scores.sort(key=lambda x: x[0], reverse=True)把命中文档按相关度从高到低排序,模拟检索接口的返回顺序。def stream_generate(query, docs, hit_idx):模拟研究员“边写边吐稿”,每次yield一小段文本而不是一次性返回全文。time.sleep(0.05)人为制造网络分块时延,模拟真实 SSE 推送的“打字机节奏”。def render_markdown(text):前台排版员:把 Markdown 记号(---、>、[n])翻译成 HTML 片段。html.append(f'<span class="cite">{line}</span>')对[1]这类引用行做特殊包裹,对应前端“角标单独渲染”的逻辑。buffer += chunk增量追加缓冲区,对应公式T_k = T_{k-1} + Δ_k。for chunk in stream_generate(query, DOCS, idxs):主循环:像reader.read()一样逐块消费生成流。print(render_markdown(buffer))每收到一块就重新渲染当前完整缓冲,对应前端的“全量渲染当前文本”。
常见误区
误区“后端把检索、生成都做好了,前端不就是接个接口把字符串显示出来吗?我直接等完整回答回来再 `innerHTML` 一塞,功能就完成了。”
正解前端是 RAG 体验的主战场。打字机流式输出、首 token 延迟、Markdown 排版、引用点击、中途停止、竞态防覆盖,全部由前端决定。就像图书馆代写服务里,书库和研究员再好,顾客看到的是前台怎么贴公告板、怎么标角标——服务体验的感知全在柜台这一侧。
为何常见:因为大模型 API 的示例大多只展示“发个请求拿完整结果”,很多人没接触过流式接口,就以为前端只是“显示字符串”。
误区“我每收到一个 chunk 就 `innerHTML += chunk`,文字立刻上屏,多简单!就是有时候界面会闪,还会报奇怪的错。”
正解每块直接拼 HTML 会引发大量 DOM 重排,还会把 AI 返回的 <script> 或 <img onerror> 当成真 HTML 执行,造成 XSS 漏洞。正确做法是:先累积纯文本缓冲区,再交给 Markdown 渲染器整体处理,渲染结果经过 sanitize 后才挂载,频率用节流控制。
为何常见:innerHTML 是“改界面最直接”的 API,初学者很容易被它的便捷误导,忽略了安全和性能代价。
误区“流式渲染又要拼 Buffer 又要处理半截 Markdown,太麻烦。我等后端全生成完,一次性显示完整答案,又能少写代码又不乱。”
正解等完整回答意味着用户盯着空白屏幕好几秒。流式输出的核心价值就是“首 token 秒回”,让用户立刻感觉系统在思考。前端可以用 requestAnimationFrame 合并分块来压缩渲染成本,骨架屏再兜底展示等待态,体验比“憋大招”好太多。
为何常见:一次性渲染在开发时最容易调试,人们本能地选择先跑通再优化,但 RAG 场景里“等待感”本身就是最大的体验杀手。
误区“我点了停止生成,界面不动了,这不就停了吗?后端还在跑有什么关系?反正我看不到。”
正解前端停止显示不代表请求结束。必须用 AbortController.abort() 真正中断 fetch,让网络连接关闭;有条件的话还要通知后端取消本次生成任务,否则算力持续消耗、费用持续累加。就像顾客喊停时,前台如果不给研究员挥旗,研究员还在埋头写,纸就白费了。
为何常见:“看不见就等于停止了”是直觉思维,但网络请求和后端推理是独立运行的两套系统,前端 UI 只是其中一端。
误区“反正渲染库够快,我每次拿到新 chunk 就把整个 markdown 字符串重新 parse 一遍再塞进 DOM,效果应该也还行吧?”
正解大模型回答动辄几千字,每个 chunk 全量重渲染会持续占用主线程,导致输入框卡顿、滚动掉帧。正确姿势是节流合并渲染帧,配合增量 diff 只更新尾部差异,代码块高亮也只在闭合后才做。渲染节奏跟不上流式节奏时,再快的库也会变成卡顿源。
为何常见:在小文本 demo 里全量重渲染确实看不出问题,但真实 RAG 回答长度和 chunk 频率都会放大性能问题。
面试题
Q1. 前端RAG中,调用后端检索接口最恰当的方式是?
- A.fetch以POST请求传递参数← 正确答案
- B.WebSocket双向长连接传输
- C.img标签拼接GET参数触发
- D.script标签执行JSONP回调
答案:A。检索接口通常用 POST + JSON body 传递 query、top_k、filter,语义清晰且能承载长文本。WebSocket 更适合全双工实时通信,检索是“请求-响应”模式,不必常驻连接;img/script 标签都是 hack 方案,不适用于带业务语义的 API 调用。
Q2. SSE协议相比WebSocket的最大特点是?
- A.服务端单向推送流式文本← 正确答案
- B.客户端服务端全双工通信
- C.高效传输二进制大数据块
- D.需要轮询才能获取新消息
答案:A。SSE 是服务端单向推送的通道,像打印机只负责“往柜台吐纸”,天然适合大模型流式输出。WebSocket 是双向的,二进制效率更高;D 说的是轮询,不是 SSE;C 描述的是 WebSocket 或二进制帧协议的特性。
Q3. 前端避免“旧回答覆盖新回答”的竞态方案有哪些?
- A.请求序号自增并只渲染最新← 正确答案
- B.每次新提问先abort旧请求← 正确答案
- C.用队列顺序展示所有响应
- D.新请求发送前锁住输入框
答案:AB。自增序号只认最新、新请求发起时 abort 旧请求,是两种主流竞态治理方案。C 排队展示会让旧响应依然出现,治标不治本;D 锁输入框只能防止连续输入,无法阻止已发出的旧请求返回。竞态的关键是“让旧响应失效”,而不是“让用户别操作”。
Q4. 打字机效果卡顿的首要原因是?
- A.等全部内容返回才渲染界面← 正确答案
- B.字体加载过慢导致首屏慢
- C.后端每个token之间间隔久
- D.用户网络带宽不足传输慢
答案:A。打字机体验的“假”往往不是网速或字体,而是前端等完整响应才一次性渲染——用户盯着空白页干等。RAG 流式的本质就是让首 token 尽快出现,边收边渲染。B/C/D 属于外部因素,不是“卡顿”的直接元凶。
Q5. Markdown实时渲染必须处理哪些问题?
- A.对HTML输出执行XSS过滤← 正确答案
- B.截断的分块加临时闭合符← 正确答案
- C.渲染节流并缓存解析结果← 正确答案
- D.每次都全量重建整个页面
答案:ABC。AI 生成内容可能夹带恶意脚本,必须 sanitize;流式中 Markdown 语法块常处于未闭合状态,要临时闭合兜底;高频分块需要节流和缓存。D 的“全量重建”恰恰是要避免的性能陷阱,正确做法是增量更新和局部 diff。
Q6. 点击引用角标跳转到来源,内部使用哪个API?
- A.scrollIntoView配合平滑滚动← 正确答案
- B.innerHTML直接替换整个内容
- C.localStorage记录阅读位置点
- D.WebWorker执行后台计算任务
答案:A。scrollIntoView({ behavior: 'smooth' }) 能把目标来源卡片平滑滚动进视口,是角标跳转的标准实现。B 是渲染内容的手段不是滚动定位;C 是持久化存储,与跳转无关;D 用于后台计算,不解决页面内定位问题。
Q7. 连续提问时,下面哪种方案能避免旧请求覆盖新内容?
- A.每次请求带自增序号按时效渲染← 正确答案
- B.把旧响应暂存用户手动选择看
- C.用定时器把旧输出均匀延迟展示
- D.关闭输入框等全部请求都返回
答案:A。自增序号按时效渲染是竞态处理的经典方案:响应回来时只认最新序号,旧响应直接丢弃。B 把选择负担丢给用户;C 用定时器打散时序会制造更大混乱;D 只是限制用户操作,已发出的旧请求依然可能覆盖新内容。
Q8. 前端RAG完整链路中,下列哪些属于前端职责?
- A.组装检索请求并发起调用← 正确答案
- B.逐块读取生成流维护缓冲← 正确答案
- C.将Markdown渲染成可交互HTML← 正确答案
- D.训练大模型参数并部署GPU服
答案:ABC。检索请求组装、流式读取与缓冲、Markdown 渲染都是前端 RAG 的核心职责。D 是后端/算法工程师的工作。前端负责的是“拿到检索结果之后展示、生成流读取、界面交互”这一整段体验链路。
Q9. 等待AI首个token期间展示占位界面,被称为?
- A.骨架屏占位降低等待焦虑← 正确答案
- B.虚拟滚动按需渲染长列表
- C.IntersectionObserver懒加载
- D.ServiceWorker离线缓存策略
答案:A。骨架屏(Skeleton Screen)是在内容加载前展示的灰色占位框,用“马上就有内容”的视觉暗示缓解等待焦虑。B、C、D 分别是列表优化、懒加载和离线缓存技术,与“首 token 等待占位”不是一个概念。
Q10. 关于前端中断RAG生成,下列说法正确的是?
- A.调用abort后fetch抛AbortError← 正确答案
- B.前端还需清理计时器与状态机← 正确答案
- C.残余网络分块需要过滤丢弃← 正确答案
- D.服务端自动感知并停止计费
答案:ABC。abort() 会让 fetch promise 以 AbortError 拒绝,前端在 catch 中要清理计时器、状态机,并丢弃停止后残余到达的分块。D 是常见误解:前端中断连接后,服务端不会自动停止大模型推理,需要后端配合取消机制才能真正停止计费。