你的 alt text 通过了自动化检查,不代表它合格:GitHub 如何构建 alt text 质量检查器
GitHub 博客工程文章(作者所属 User Experience 团队)讲述他们构建 alt text 质量检查器的过程与取舍:自动化工具(如 axe、Lighthouse)能检查"有没有 alt",但判断"alt 好不好"是另一回事。GitHub 的方案是 5 条确定性规则(默认开启,无需调用模型)加 1 条可选 AI 规则(用视觉模型按页面上下文评判)。本文基于该文章,梳理这套检查器的设计。
为什么"通过检查"不等于"质量合格"
常规可访问性检查只能确认属性存在:有 alt、非空、长度达标。但 alt="image" 这种空话能通过所有自动化检查,对屏幕阅读器用户却毫无信息量。GitHub 想判断的是字符串本身是否携带信息——这是语言层面的判断,不是 DOM 层面的。
确定性规则:宁漏勿误报
5 条规则默认开启,不需要模型调用或网络请求。关键设计原则:质量检查器成败在于误报率,所以用封闭集合而非启发式。
- 模糊 alt 规则:把字符串规范化后与精心维护的"无信息量词表"精确匹配。alt="image" 被标记;alt="image of the login screen with the SSO button highlighted" 不标记。作者明确选择"放过漏报"——一个可靠的、开发者愿意长期开启的检查器,胜过动不动误报而被关掉的检查器。
重复检测是布局问题,不是 DOM 问题
一排五个"3/5 stars"图标,屏幕阅读器用户会听到五遍相同内容。最初版本按文档顺序检测连续重复的 alt,结果误报:页头和页脚的 GitHub logo 在提取列表里相邻,但屏幕上离得很远,用户不会把它们当一组。
修复:规则改为按页面布局判断——只有当两个元素包围盒的间隙相对盒子尺寸足够小才扩展"重复段":
const gap = Math.max(horizontalGap, verticalGap)
const largerDim = Math.max(a.boundingBox.width, a.boundingBox.height,
b.boundingBox.width, b.boundingBox.height)
return gap > GAP_MULTIPLIER * largerDim
两个细节:GAP_MULTIPLIER 是凭经验调出来的判断值,不是来自规范;当图片没有可测包围盒时检查"fail open"(视为同组继续)——漏掉一个发现是隐形的,错误的发现不是。
让模型当评审员而不是批评家
可选 AI 规则用视觉模型(通过 GitHub Models)评判。上下文提取:最近标题、页面标题、figcaption、图片是否在链接/按钮内、附近 600 字符正文。链接信号最重要——图片是链接唯一内容时,alt 就是链接的可访问名称。
最初的失败模式不是模型读错图,而是模型"有观点":对完全合格的 alt 也建议改一版,因为"还能更好吗?"这种问题语言模型总是答"是"。三个修复:
- 决策流程替代指令:提示词走四步有序判定,命中第一步即输出结论——装饰性、与说明文字冗余、功能性、信息性
- 显式反挑刺规则:信任作者框架;区分冗余前缀("Image of…")和语义前缀("Photograph of…");周围正文已分析图片时,短 alt 即正确
- 结构化输出 + 强制字段顺序:先输出推理再输出结论,让模型在选标签前先构建论证
这不会让模型永远正确,但让它稳定到可以迭代:仓库带有基于已发布教学数据的离线评分 harness。
关键实现细节
- 用 Playwright 的 role-based locator 而非 querySelectorAll('img'):不在可访问性树里的元素(包括 alt="" 的装饰性图片)直接排除——空 alt 是作者明确声明"这是装饰",标记它会惩罚恰恰想鼓励的行为
- 扫描以网页为单位,图片上下文连同图片一起交给视觉模型
总结
GitHub 这套 alt text 检查器的启示有三层:一是"检查存在性"和"检查质量"是两个问题,质量判断需要语言理解;二是自动化规则的正确姿势是封闭集合 + 容忍漏报,用可靠性换采纳率;三是视觉模型当裁判时要防"什么都想改"的偏见,用决策流程 + 结构化输出把它约束成一致的评审员。对做无障碍工程或质量检查工具的同学,这是很实用的设计参考。
来源:https://github.blog/engineering/user-experience/your-alt-text-passes-automated-checks-that-doesnt-mean-its-any-good/