Streamlit 错误框内嵌 “Install skills“ 引导:在报错瞬间引导开发者安装 Agent Skills
2026/9/19 8:00:45 网站建设 项目流程

Streamlit 错误框内嵌 "Install skills" 引导:在报错瞬间引导开发者安装 Agent Skills

【免费下载链接】streamlitStreamlit — A faster way to build and share data apps.项目地址: https://gitcode.com/gh_mirrors/st/streamlit

导读

本文基于 Streamlit 仓库中的产品规格文档 specs/2026-06-26-in-error-install-skills-nudge/product-spec.md,完整讲解 Streamlit 新增的"错误框内 Install skills 引导(callout)"功能:当本地开发时开发者触发由 Streamlit 自身抛出的异常(如StreamlitAPIException),错误框正下方会贴心地出现一个可一键安装 Agent Skills 的提示卡片,让 AI 编程助手"学会"修复这类错误。读完本文,你将掌握该功能的触发条件、三种界面状态与文案、与启动 toast 的互斥逻辑、前后端实现机制(含Exception.proto新增字段),以及仓库中对应的 E2E 测试如何验证这些行为。

背景:为什么要在"报错时刻"引导安装 Skills

Streamlit 随包捆绑了面向 AI 编码助手的 Agent Skills(见 lib/streamlit/web/skills.py,由streamlit skillsCLI 安装,其设计规格见 specs/2026-05-11-streamlit-skills-cli/product-spec.md),能让 AI 助手在构建和调试 Streamlit 应用时表现更好。但问题在于:大多数开发者根本不知道这些 Skills 存在,也没有一个恰当的时机去提示他们安装。

因此该功能是 Streamlit Agent Skills 的两个采用入口(adoption surface)之一:

  • 第一个入口:启动时的主动 toast 引导(规格中引用的 PR 15473,已获批准),在应用加载时就弹出。
  • 第二个入口(本文主题)错误框内的一键安装引导,在开发者真正"踩坑"的瞬间出现。

两者的区别在于"意图强度"(intent):

  • 启动 toast 是**主动式(proactive)**提示,出现在开发者遇到任何摩擦之前——这是一个低意图时刻,容易被随手关掉并忘记。
  • 未捕获的 traceback 是开发者意图最强的时刻:这正是他们最希望自己的编码助手更懂 Streamlit 的时候。错误框本身已经会引导用户求助外部帮助("Ask Google""Ask ChatGPT"),在同一个位置提供"Install skills",并配上文案"so your AI assistant can fix errors like this",就能把沮丧情绪转化成一个一键完成的设置步骤,而无需额外维护一个新组件。

哪些错误会触发引导?

并非所有 traceback 都是 Skills 能帮上忙的。开发者自己逻辑里的ZeroDivisionErrorKeyError,装再多 Streamlit Skills 也修不了——在那种错误上提示"安装 skills"纯属噪音。因此引导被严格限定在Streamlit 自身抛出的错误

  • StreamlitAPIException以及其他 Streamlit 定义的异常类型,全部是基类streamlit.errors.Error的子类;
  • 这类异常意味着开发者误用了 Streamlit API——正是 Skills 存在要预防的错误类型,所以"fix errors like this"在这里是诚实的承诺。

规格文档中记录了一个明确的决策(2026-06-29,产品经理 Johannes Rieke):内嵌引导只对 Streamlit 抛出的异常显示,而不是最初的草案中那样对所有未捕获错误都显示。

收窄范围并不会损失触达:启动 toast 仍然为所有人承载广泛的"安装 skills"提示,因此遇到普通 Python 错误的开发者已经被提示过一次;错误框内引导是高意图的强化,只在 Skills 真正能起作用时才出现。

交互设计:与错误框"绑定"的三态卡片

布局原则

引导是错误框正下方独立的一个小卡片,与错误框共享同一套颜色(tint)、圆角半径和内边距:一个 sparkle 图标、一行文案、一个轻量的下划线文字按钮。两个框之间的间距比 Streamlit 元素间的常规间距紧一个步进(one gap step tighter),从而在视觉上读作"成对出现"——引导属于这条错误。

动作按钮是文本链接,与错误框自身的Copy / Ask Google / Ask ChatGPT链接风格一致,让 CTA 读起来像"平级"元素而不是压倒它们的面板。按钮位于卡片右边缘,正好与上方的Ask ChatGPT对齐,且当文案换行到多行时保持位置稳定。

窄容器适配:图标与文案作为一个整体单元,图标永远不会被孤行丢弃。当容器宽度低于约 250px(接近最小宽度的侧边栏、五列布局的一列)时,动作按钮换到自己独立的一行,而不会被挤压或推出卡片。规格说明已验证到 100px 宽仍可正常工作——远低于 Streamlit 实际会产生的任何宽度。

三种状态

Idle(待安装)——错误框与其引导并排显示。下面是规格文档中的实现渲染图:

Success(安装成功)——卡片整体切换到成功色调做短暂确认,然后自动消失。切换整个卡片(而不是只换文字颜色)是为了避免"绿色文字坐在红色错误框里"的误读:

上面的渲染图是兜底文案——仅在服务端没有返回细节时显示。真实安装通常会返回细节(如 "Installed to .agents/skills."),此时会优先显示服务端细节(见下表)。

Error(安装失败)——卡片保持错误色调,显示服务端给出的失败原因和一个Retry动作。原因由服务端提供,可能长达多行(它会点名是哪些路径阻碍了安装),因此原因在行内换行,而图标与Retry保持位置固定——Retry始终在右缘、相对多行原因垂直居中:

这个状态是刻意难以触达的:nudge_suppression_reason()会在安装到每个目标都会被阻止时(reason 为conflict,经由_one_click_install_would_be_refused())完全隐藏推荐,因此服务端从不提供一个"注定失败"的安装;只在部分目标冲突时,会安装其余部分并报告为部分成功。但这一门槛只在构建NewSession消息时评估一次——因此**"被推荐之后、点击之前"**新出现的阻塞目标仍然会导致失败,这个竞态正是 Error 状态存在的意义。e2e_playwright/skills_install_callout_test.py 通过在页面加载完成后再阻塞目标,确定性地复现了它。

各状态文案对照表

规格文档给出的完整文案表:

状态文案动作
IdleInstall Streamlit's skills so your AI assistant can fix errors like this.Install skills
InstallingInstalling Streamlit's skills…Installing…(不可用)
Success<server detail>(如 "Installed to .agents/skills."),否则 "Skills installed — your AI assistant is ready to help."(自动消失)
ErrorCouldn't install skills.<server reason>(如 "… already exist. Remove them and try again.")Retry

Installing 状态使用自己的句子而非复用 Idle 的推销文案,因为文案是礼貌的 live region,是屏幕阅读器用户获知"点击已生效"的唯一途径。Success 优先显示服务端细节:它既报告 Skills 装到了哪里,更重要的是点名哪些被跳过了——部分安装永远不会被确认为完整安装(toast 表面同样如此)。从后端实现看,冲突信息由 lib/streamlit/web/skills.py 中的_conflict_error()生成,它只暴露<harness>/skills/<skill>这类精简路径尾部(_concise_install_paths),避免把绝对路径或原始OSError字符串泄漏到浏览器。

键盘与无障碍细节

  • 动作按钮在不可用时用aria-disabled报告,而绝不用disabled属性——disabled 按钮不是可聚焦区域,浏览器会在交互中途失焦,刚按了 Enter 的开发者会被送回文档顶部,而重新启用后的Retry(同一个元素)不重新 Tab 就无法触达。
  • live region 只覆盖文案:role="status"隐含aria-atomic,如果 region 同时覆盖动作按钮,每次文案变化都会重读整段推销文案。
  • 引导的出现刻意保持静默——一个插入时内容已就位的 live region 不会触发播报——所以不请自来的 CTA 绝不会打断正在进行的朗读;它在线性阅读和 Tab 顺序中均可达。
  • 焦点环使用currentColor而非应用的focusRingtoken,因为后者是针对页面背景调校的,在红色色调的告警上会跌破 3:1 最小对比度。

行为规则:何时显示、何时不显示

显示的全部前置条件

以下条件必须全部成立:

  1. 仅限本地开发。复用已用于门控 "Ask ChatGPT" 链接的同一个判定(shouldShowLinks):直接回环(localhost)连接、非嵌入、不在 Community Cloud / SiS 上。
  2. 错误而非警告!element.isWarning)。
  3. Streamlit 抛出的异常。只有 Streamlit 自身定义的异常(StreamlitAPIExceptionstreamlit.errors.Error的其他子类)才符合;ZeroDivisionErrorKeyError等任意用户/运行时错误触发。后端在异常 proto 上标记该属性。
  4. 错误未被完全脱敏client.showErrorDetails="none"会隐藏类型、消息与 trace,因此标记一并被隐藏——引导绝不在"错误框拒绝描述错误"时提供修复。
  5. 错误是可见的。折叠的st.expander或不活动的st.tabs面板内的错误虽然保持挂载,但不得占用唯一的引导槽位。
  6. 检测到 AI 编码助手,且本会话尚未安装Skills,且本会话没有失败过的安装——失败原因通常是环境性的,反复推荐会在后续每个错误下面堆放一个无法关闭的新卡片。
  7. 启动 toast 当前不在显示中(两者互斥)。
  8. 用户未永久关闭该引导(toast 上的 "Don't show again")。

streamlit.errors.Error是"Skills 能帮上忙"的近似而非精确代理

它宁可少显示,也不显示错的东西:

  • 少数 Streamlit 抛出的错误并不继承Error(如st.image路径缺失引发的MediaFileStorageError、不可读persist="disk"条目引发的CacheError),所以它们得不到引导,即便 Skills 本可能帮上忙。重新调整这些类的继承关系值得做,但不在此范围内。
  • 反过来,少数Error子类并非 Skills 可修复的——MessageSizeError需要调大server.maxMessageSize,而不是更好的 Streamlit 知识——此时引导会出现但无济于事。代价只是在 localhost 上多一个 CTA。

与启动 toast 的互斥

  • 引导绝不与 toast 同时出现
  • 但 toast 被延迟(snooze,24 小时)后引导仍会出现——24 小时延迟被刻意检查:错误是比"主动式启动提示被延迟"更高意图的时刻。
  • toast 上的永久"Don't show again"对两个表面都立即生效(引导的闸门每次渲染都读取该关闭标记),但它只存在于 toast 上。因此在 24 小时延迟窗口内,引导没有自己的关闭开关;toast 在延迟结束后回归并提供永久关闭选项。这是被刻意接受的:引导被限定在 Streamlit 抛出的错误上,这正是最可能想要这个提议的时刻,且它不会堆叠或重复播报(一个粘性槽位;异常元素跨 rerun 保持而非重挂载)。

同一时刻只显示一个引导

当屏幕上有多个错误时,一个**粘性认领槽位(sticky claim slot)**会去重为恰好一个引导。认领不会被安装后的状态变化中途"拽走",因此成功/失败消息始终附着在用户点击的那条引导上。

安装流程

点击Install skills执行与 toast 完全相同的安装动作(InstallSkillsHandler/requestInstallSkills)。成功时引导短暂显示 "✓ Skills installed" 后自动消失;失败时显示服务端原因与Retry

引导没有自己的关闭控件——它已被紧密门控且在成功时自毁;永久退出开关在 toast 上。

实现机制:从后端标记到前端卡片

后端:Exception.proto新增一个布尔字段

为了让前端只对 Streamlit 定义的错误显示引导,前端必须知道异常是否由 Streamlit 定义。后端在marshall期间本来就知道(isinstance(exc, streamlit.errors.Error)),因此只需把它暴露为Exception.proto上的一个新布尔字段is_streamlit_exception。见 proto/streamlit/proto/Exception.proto:

// True if Streamlit itself raised this exception (a subclass of // streamlit.errors.Error, e.g. StreamlitAPIException) rather than an // arbitrary user/runtime error like ZeroDivisionError. The frontend uses // this to scope the in-error "Install skills" callout to mistakes the // Streamlit agent skills can actually help with. Additive and // backwards-compatible: proto3 defaults it to false, so existing external // consumers of this (deliberately stable) proto are unaffected. bool is_streamlit_exception = 7;

这是向后兼容的:proto3 默认值为falseExceptionproto 被刻意保持稳定(文件注释明确说明它被外部服务使用),既有外部消费者不受影响。规格文档记录:曾考虑用前端异常类型名白名单(字符串匹配)来实现,但被否决——它很脆弱(会漏掉不带Streamlit前缀的类型如DuplicateWidgetID,且alternate_name覆盖可以完全替换type)。

前端:复用 toast 的安装后端

唯一的全新接线是一个SkillsInstallContext(lib 核心层),它把 app 层的安装回调下发给 lib 层的ExceptionElement——镜像了LibConfigContextshowErrorLinks供值的方式。不引入任何新的 lib→app 依赖。

遥测

给现有的安装MetricsEvent增加一个surface维度(toastvserrorCallout),使"展示 → 安装"漏斗可按表面归因。

手动调试的限制

make debug无法演示该功能:debug 目标以--server.headless=true运行应用,而nudge_suppression_reason()对 headless 返回"headless"——同时抑制 toast 和引导。要手动体验,你需要:

  1. 一个非 headless的服务器;
  2. 一个包含 AI 助手配置目录(如.claude)且未安装 skills的临时HOME
  3. 从本 checkout之外的项目目录运行应用(skill 检测会扫描应用目录、其 git 根目录和最近的 agent-config 祖先目录,所以在仓库内原地运行会检测到仓库自带的 skills)。

e2e_playwright/shared/skills_install_app.py中的start_agent_home_app_server精确构造了这套环境,是手动运行的参考实现。它创建含.claude目录("agent present" 信号)的临时 HOME、预置空凭据(credentials.toml使非 headless 启动不弹提示)、把测试 app 脚本复制进隔离的临时项目目录,并以--server.headless false加隔离的HOME/USERPROFILE环境变量启动服务器——让"有 agent、无 skills"成为可复现的确定性状态。

E2E 测试如何验证这套行为

specs/2026-06-26-in-error-install-skills-nudge/product-spec.md 中描述的所有关键行为,都能在 e2e_playwright/skills_install_callout_test.py 找到对应的 Playwright 断言:

  • 互斥:toast 可见时(stSkillsNudge),三个stException已在屏上,但引导计数为 0;关闭 toast 后引导才出现(test_skills_install_callout_shows_below_one_error_box)。
  • 去重:两个合格的 Streamlit 异常在场,引导恰好一个,且附着在第一个合格错误(streamlit_error_first)上。
  • 错误范围门控:先渲染的普通ValueError(用户代码错误)所在的user_error容器没有引导——断言依赖is_streamlit_exception门控而非"槽位已被占用"。
  • 布局:引导是错误框下方的独立盒子(stException过滤含引导的计数为 0),有独立的stSkillsInstallCallouttest id,无 ✕ / snooze / "Don't show again" 按钮。
  • 真实端到端安装:点击 "Install skills" 后等待 "Installed to" 文本(test_skills_install_callout_installs_end_to_end,仅 chromium 运行,因为安装是浏览器无关的后端操作),并截取 success 快照。
  • 失败路径:通过向项目目录种植.agents/skills/developing-with-streamlit.claude/skills/developing-with-streamlit真实目录,复现"offer 之后目标被阻塞"的竞态,断言显示 "already exist" 原因与Retry、且绝不显示 "Skills installed"(test_skills_install_callout_reports_a_failed_install)。

测试 app e2e_playwright/skills_install_callout.py 用 keyed container 组织错误,使测试能精确定位引导附着在哪个错误上。此外,夹具注释特别说明该 fixture 是函数级而非模块级:端到端测试会真实安装进临时 HOME,若共享服务器,安装会泄漏给同一 xdist worker 上的兄弟测试(服务器不再推荐引导,后续测试会失败)。

范围外与后续事项

  • SCRIPT_COMPILE_ERROR模态框(语法错误):全屏编译错误模态框是独立表面,在该处加入引导是已记录在案的后续事项。
  • 非 localhost / 托管环境:刻意排除——Skills 的目标是本地编码助手。

规格文档的验收清单确认:在 SiS/Cloud 上被刻意抑制(与 "Ask ChatGPT" 链接同一闸门);无破坏性 API 变更(一个可加性、向后兼容的 proto 字段,无公开 Python API 变更,复用既有安装 handler);无新依赖;遥测到位;安全影响低(本地文件系统操作,仅在直接回环连接上门控,复用 toast PR 的InstallSkillsHandler);文档改动最小——streamlit skillsCLI(separate spec)才是文档化的入口点。

小结

"错误框内 Install skills 引导"是 Streamlit 在开发者最需要帮助的时刻主动承接用户的设计:通过Exception.proto上单个向后兼容的布尔字段完成"仅 Streamlit 错误"的精准门控,通过复用一个InstallSkillsHandler让 toast 与错误框引导共享同一安装后端,再以粘性槽位保证"同一时刻仅一个引导"。它刻意不做打扰——出现静默、不可关闭、自动消失——只在真正能帮上忙的错误上,把一次沮丧变成一次点击。对于想在本地复现或为其贡献代码的开发者,仓库中的 skills_install_callout_test.py 与 skills_install_app.py 是理解全部行为约定的最佳起点。

【免费下载链接】streamlitStreamlit — A faster way to build and share data apps.项目地址: https://gitcode.com/gh_mirrors/st/streamlit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询