构建通用无障碍代理——过程中的经验教训
Building a general-purpose accessibility agent—and what we learned in the process
GitHub 正在试点一个实验性通用无障碍 agent,旨在为工程师提供即时的无障碍问题解答,并自动发现和修复代码中的无障碍问题。该 agent 已审查 3,535 个 pull request,解决率为 68%。agent 采用子 agent 架构,包括被动审查者和主动实施者两个专门子 agent,并通过模板模式传递内容以保持一致性。agent 被设定为理解其局限性,避免处理高风险模式和高复杂度代码,并定期接受手动评估以改进指令。
说 agent(智能体)已成为一种流行的代码工作方式,这还算是轻描淡写。GitHub 已在多项计划中采用基于 agent 的代码创建和编辑方式,包括试点一个 agent 来帮助我们履行无障碍承诺。GitHub 目前正在试点一个实验性的通用无障碍 agent,以实现两个主要目标:在 GitHub Copilot CLI 和 Copilot VS Code 集成中,为工程师提供可靠、即时的无障碍问题解答;在简单、客观的无障碍问题进入生产环境之前,自动发现并修复它们。针对第二个目标,该无障碍 agent 被设置为自动评估修改前端代码的变更。迄今为止,该 agent 已审查了 3,535 个 pull request,解决率为 68%。按出现频率排序,前五类问题集中在:向辅助技术清晰传达结构和关系;为交互控件提供清晰简洁的名称;确保用户知晓重要通知;确保非文本内容有文本替代方案;按逻辑顺序在页面和视图中移动键盘焦点。每一类问题都代表着自动消除的摩擦和障碍,否则这些障碍会阻碍使用和依赖辅助技术的用户使用 GitHub。以下是其运行时的截图:
感兴趣吗?我们将概述这项实验中的成功经验和教训,希望能对其他团队的无障碍之旅有所帮助。
什么是 LLM 和 agent?
注意:本文假设读者对 LLM、agent 及其相关概念有一定了解。如果不熟悉,可以在我们的博客上了解更多关于 LLM 的信息。以下是一些可能有帮助的特定文章:
- A guide to deciding what AI model to use in GitHub Copilot
- GitHub for Beginners: How to get LLMs to do what you want
- How to write a great agents.md: Lessons from over 2,500 repositories
- Multi-agent workflows often fail. Here’s how to engineer ones that don’t.
思维方式
社会残障模型告诉我们,访问障碍——以及随之而来的损伤——可能因环境的构建方式而产生。同样的思维也适用于数字体验。通过无障碍 agent,我们并非试图孤立地“解决”无障碍问题。相反,我们试图增强同事们的努力,更好地帮助他们消除因我们构建 GitHub 用户界面方式而可能产生的障碍。无障碍 agent 并非能自动处理所有假设场景的“银弹”。理解、尊重并传播这一点,有助于更好地设定 agent 的责任范围。这加速了实验的启动,并为此项工作赢得了更多支持。
过往努力
《欧洲无障碍法案》现已生效。《美国残疾人法案》第二章将于 2027 年 4 月将满足 WCAG 2.1 AA 标准确立为法律上的完成定义。LLM agent 能够读取无障碍树并对其采取行动。直白地说:如果组织尚未投资于手动识别和修复无障碍问题,它们将处于劣势。这有很多原因,包括构建无障碍 agent。就此而言,GitHub 已建立了一套成熟的系统来记录无障碍问题,并验证问题修复是否按预期工作。这包括:
- 用于报告问题的结构化模板
- 重现问题的步骤
- 关于问题严重级别、服务区域和适用 WCAG 成功标准的丰富元数据层
- 与处理该问题的 Pull Request 的交叉链接
- 验收标准
此外,所有问题都集中在一个单一的仓库中。虽然这种问题记录工作早于 LLM 工具流行的爆发期,但其高度一致和结构化的特性使其成为无障碍 agent 参考的理想内容语料库。因此,我们指示 agent 调查这些问题,看是否能从中推断出相关的代码和语言片段。这是 LLM 非确定性的“模糊匹配”行为成为资产而非潜在责任的一个领域。
旧金矿
与任何其他专业领域一样,技能文件中的模糊指令是不够的。告诉 LLM“使用无障碍最佳实践”并附上一小段示例列表,效果不会好。在生成代码时,LLM 不幸地倾向于产生无障碍反模式,因为目前每个主要的 LLM 都是在数十年的不可访问代码上训练的。为了抵消这一点,agent 需要更好的内容来参考。因此,我强烈建议投资于手动编目和修复无障碍问题。取得一些进展后,这些数据可以整合到 agent 中。这些问题及其对应的 pull request 为 LLM 提供了高度情境化的示例,这些示例使用其部署组织设定的约定编写。这个问题和代码的集合是 agent 迄今为止最强大的资产之一。
高效的 Token 消耗
无障碍是一个整体性问题,涉及代码、设计、文案以及创建用户界面的众多其他学科。许多无障碍工作也具有高度的情境性,这意味着通常需要了解问题的完整工作画面,才能给出适当的建议。由于这两个因素,通用无障碍 agent 在执行工作时可能会消耗大量 token。这会导致三个负面结果:不可靠输出增加、响应时间变慢、运营成本上升。在构建 agent 时保持严谨非常重要。以下是我们如何做到这一点的。
使用子 agent
无障碍 agent 最初是一个单一的 monolithic agent,但很快就超出了这种方法的限制。因此,我们将其演变为使用子 agent 架构。许多指南建议创建一整套子 agent,每个都有其特定的职责领域。在这里,子 agent 并行执行,主 agent 协调它们的输出。令人惊讶的是,这种方法对无障碍 agent 来说适得其反。相反,我们最终使用了两个专门的子 agent:
- 第一个子 agent 充当被动审查者和研究者。
- 第二个子 agent 充当主动实施者。
这两个子 agent 被沙箱化,不能直接相互传递内容。相反,它们生成结构化的、模板化的输出。然后,这个输出被提供给父级编排无障碍 agent 进行消费、验证和路由。采用这种方法有几个原因:
- 升级检查点:审查者检查可能需要人工干预的领域。这包括多个高严重性的 WCAG 失败,以及一系列已知难以实现无障碍的模式。
- 基于复杂度的行为:如果底层代码被认为过于复杂,agent 会被指示以专门的仅指导模式运行。在这里,父级无障碍 agent 充当仲裁者,而审查者 agent 是“无意见的”,仅按指示报告发现。
- 过滤:审查者呈现其发现的所有内容。然后,父级无障碍 agent 利用资源和技能来确定哪些内容与请求相关。审查者将其所有发现传递给实施者将成本高昂,并可能使其陷入无关且适得其反的任务。
- 可追溯性:子 agent 之间的直接通信将消除创建和审查用户及 agent 决策审计追踪的能力。考虑到 agent 关于复杂模式的指令以及无障碍工作的高度情境性,这一点很重要。
按线性顺序执行指令
除了是一个整体性问题,有效的数字无障碍工作还需要有条理、注重细节的方法。使用子 agent 来提高 LLM 回复速度的考虑,与我们对其结果准确性的需求相平衡。我们发现,强制 agent 按固定顺序执行其子 agent 指令是关键。我们首先建立一组有序的父阶段。每个阶段本身包含子级的、有序的指令步骤,并附有相关的资源和技能:
这种线性顺序的有趣之处在于,它反映了我个人处理审计、修复和报告职责的方式。
使用模板模式传递子 agent 内容
沙箱子 agent 的整个操作都围绕模板模式文件构建。这些文件创建了一致性,对于保持 agent 专注和按计划进行至关重要。两个模式模板是:
- 审查者模板模式:关注审计什么,以及如何找到关于它的适用信息。
- 实施者模板模式:关注修复什么以及如何修复。
如果没有模式文件,所有 agent 都会尝试任意地相互通信。这将导致 token 支出增加、不良幻觉、不必要的代码更改,以及 agent 审计行为变得困难甚至不可能。
承认局限性
创建无障碍 agent 的另一个关键方面是理解 agent 可能不足的领域。由于 agent 并非一个现成的无障碍“解决方案”,我们希望避免出现 agent 的错误输出未被使用它的人类充分质疑的情况。当某人不熟悉数字无障碍考虑和实践时,这一点尤其重要。以下是我们为适应 agent 的局限性所做的:
评估代码复杂度
我们希望避免需要执行昂贵且耗时的工作来重新审视 agent“认为”可访问但实际不可访问的解决方案的场景。为了解决这个问题,无障碍 agent 使用一个小型 shell 脚本来分析它要处理的代码。脚本本身很简单,使用一组基本的启发式规则来评估相对复杂度,并将其提炼为一个分数。然后 agent 会接收这个分数。如果分数超过设定的阈值,agent 会被指示不执行代码更改。相反,它会通知使用 LLM 的人,他们应该联系无障碍团队咨询他们正在尝试做的事情。
识别高风险模式
理解这一点很微妙,但要知道,完全有可能存在通过自动无障碍检查但在功能上却无法使用的代码。作为代码复杂度的补充,无障碍 agent 被指示避免为无障碍团队已识别为高风险的模式尝试生成代码。这包括但不限于:拖放、toast 通知、富文本编辑器、树视图和数据网格。这些模式需要大量的专注和细节,目前超出了 LLM 以真正能与辅助技术配合的方式生成它们的能力。不禁止高风险模式和高复杂度代码环境将导致不必要地消耗每个人的时间来重新处理,并且也代表无障碍团队的信誉风险。我们通过关闭 LLM 走这条路径的能力来避免这种情况。
减少行动偏见
我不喜欢将 LLM 拟人化,但它们似乎都有一个共同特质:极度渴望生成内容。对于 Copilot 来说,这通常意味着生成代码。我们必须创建反作弊指令,以防止 LLM 在需要人类专业知识时,通过巧妙的方式绕过其不生成代码的指令。这阻止了它违反自己的干预指令。
要知道,可程序化确定的问题并不能涵盖一切
Agent 的成功指标存在于一个更大的背景下。在总共 55 个 WCAG A 级和 AA 级成功标准中,只有 35 个可以通过确定性自动代码检查器检测到。这意味着大约 36% 的 A 级和 AA 级成功标准无法自动发现。基于 LLM 的 agent 操作正在缩小这约 36% 的差距,但这并非一门完美的科学。因此,在设计阶段和原型制作阶段(大多数无障碍问题的起源地)尽早手动识别无障碍障碍变得非常重要。这种思维也反映在 agent 的升级逻辑中,即无障碍团队的成员可以与设计师合作,帮助考虑替代方法并集思广益,找到在不牺牲无障碍性的情况下实现业务目标的解决方案。这种干预和帮助是为了阻止潜在的下游问题——以及昂贵且耗时的重新设计——在它们有机会启动之前就被扼杀。
手动评估 agent 输出并调整不符合预期的部分
我们定期对手动审查 agent 输出,以确定其准确性和有效性。此外,我们还有工具来捕获 pull request 审查者的反馈。两者都是 agent 需要更好指令以及需要新资源和技能的领域的强烈信号。
公开学习
总结一下,我们了解到该 agent:
- 用于辅助和增强现有的无障碍工作,而非取代它们。
- 当针对你的特定体验,在手动审计和修复的无障碍问题上进行训练时,效果显著提升。
- 在利用子 agent 时,token 消耗效率更高。
- 当以有条理、线性的方式执行指令时,更准确、更有效。
- 当使用预格式化模板传递信息时,一致性更高。
- 被设定为理解其局限性,并将人们引导至替代支持系统。
- 通过定期审查其输出以识别需要更好指令的领域而得到改进。
这段旅程也尚未结束。无障碍 agent 仍在不断迭代,希望能帮助确保 GitHub 对所有开发者来说都是一个可访问和包容的平台。我们希望最终能将这个 agent 开源,作为我们帮助大规模改善开源软件无障碍性的承诺的一部分。在此之前,我们希望分享我们在这一过程中的经验教训,能让其他团队在开展自己的无障碍工作时有一个可供参考的资源。
本文《Building a general-purpose accessibility agent—and what we learned in the process》最初出现在 The GitHub Blog 上。