最近 Hacker News 上有一个提问很有意思:既然无障碍法律已经要求网站、App 对读屏软件这类辅助技术开放,为什么不同样扩大无障碍法律的适用边界,把“使用 AI 的权利”也写进去?
这个问题初看像法学生式的思想实验,但它背后藏着一个正在发生的技术变化:AI 正在从“可选的效率工具”变成一部分人参与数字社会的基本通道。对很多残障用户来说,AI 不是“锦上添花”,而是“没有它就寸步难行”。如果一个盲人要理解一张复杂的产品截图,以前的工具链是 OCR 加人工核对,现在给多模态大模型发一句话就能得到结构化的描述;如果一个手部运动受限的用户无法打字,语音输入配合文本生成就是他的键盘。
如果这种能力被付费墙、产品设计缺陷或模型偏见挡在外面,那么把它理解为一种无障碍诉求,其实是顺理成章的。本文不打算预测哪个国家、哪部法律会怎么修订,那超出了技术博客的边界。我更想把这个问题翻译成工程师能动手的事:当 AI 承担起辅助技术的角色,前端语义、API 设计、模型输出和测试验收到底应该怎么改?
这篇文章会先讲清楚为什么“使用 AI 的权利”会成为一个真问题,再拆解它落地时的四个技术断点,最后给出一套可以照做的工程示例,包含后端接口、前端无障碍接入、流式播报处理和效果验证。对正在做 AI 应用开发、Agent、大模型产品或信息系统无障碍改造的开发者来说,这篇文章或许能帮你提前避开未来三到五年的合规与技术债。
1. 当“使用 AI”成为一种无障碍诉求
先看一个具体场景。一位全盲的办公室职员收到一张项目截图,里面有几个图表和数据。传统读屏软件能读出按钮文字、标题层级,但读不出“柱状图的趋势走向”,也读不出“截图里的红色标注对应哪一行数据”。过去他只能求助同事,或者把图片交给第三方人工描述服务,等上几个小时。现在他可以把图片粘贴进支持视觉问答的 AI 对话框,让模型告诉他图表的趋势、异常值和上下文。
再看另一个场景。一位患有运动障碍的用户无法使用普通键盘,过去他依赖眼动仪和慢速的逐字输入。现在如果系统内置了高质量的语音识别和语句补全,他的输入效率能提升数倍;但如果系统只提供纯文字聊天框,不支持语音、不支持辅助开关,他依然会被困在输入这一环。
这些场景说明,AI 对残障群体的意义和普通人完全不同。普通人用 AI 是省时间,残障用户用 AI 是补障碍。当他们需要的是“读图”“听懂语音”“简化长文本”这类能力时,AI 实际上已经承担了传统辅助技术的功能,只是大家还没把它写进无障碍的法律与规范清单。
所以那个 HN 提问真正有价值的并不是一个“应该不应该”的价值判断,而是它提醒我们:如果越来越多的社会服务把 AI 当成默认入口,那 AI 服务的可访问性就不能只靠厂商自觉。过去无障碍法律要求的是“产品不要挡住辅助技术”,现在的趋势是“产品里的 AI 能力本身要具备可访问性”。这种转变对开发团队意味着什么?意味着今天你做的每一个 UI 设计、接口返回、流式渲染方案,都可能成为明天验收清单上的一条硬指标。
我自己的判断是:法律条文会不会通过、什么时候通过,不是工程师能决定的;但“什么时候做无障碍 AI 改造”,开发团队是可以决定的。迟到的无障碍改造一定比提前设计贵几倍,因为在功能已经上线后再回填语义标签、重构接口返回格式,几乎等于重做一遍。
2. 先厘清概念:无障碍、辅助技术与 AI 的三层关系
要理解“无障碍与 AI”这个话题,需要先把几个经常混用的词拆开。
无障碍(Accessibility,常简写为 a11y)指的是产品或服务能够让残障人士感知、操作和理解。它不等于“可用性”,一个按钮做得再好看、点击区域再大,如果读屏软件无法聚焦,它在无障碍层面就是失败的。传统的信息无障碍标准,比如 WCAG,围绕四个原则展开:可感知、可操作、可理解、健壮性。
辅助技术(Assistive Technology,AT)是用户用来访问产品的工具,包括读屏软件 NVDA、JAWS、VoiceOver、TalkBack,也包括屏幕放大器、替代键盘、眼动仪、开关控制设备等。无障碍法律的常见做法,并不是强制所有人使用某种辅助技术,而是要求产品不要阻碍这些技术发挥作用。
这里就牵扯出一个容易被混淆的点:AI 和辅助技术的关系有两种。
第一种,AI 功能可以内嵌到你的产品里,让你的产品本身变得更容易访问。例如一个图片问答按钮、一个长文本“一键简化”按钮,它们是无障碍功能。这种情形下,法律的关注点是“AI 功能入口是否可操作、结果是否可理解”。
第二种,AI 服务可以被外部辅助技术调用,成为辅助技术链路上的一环。例如读屏软件调用云端的图片描述 API,替用户生成替代文本。这种情况下,AI 不再是产品里的一个按钮,而是一个被其他工具依赖的基础服务,它需要稳定、可预期、输出规范,否则所有下游辅助工具都会被带偏。
无障碍法律的历史演进,大致遵循一个规律:从物理空间扩展到数字产品,再逐步扩展到算法服务。各国对“数字产品”的约束范围已经在扩大。例如美国 ADA 相关的诉讼实践中,网站和移动应用的可访问性已经成为重点;欧盟的《欧洲无障碍法案》把服务也纳入了强制范围;国内的《无障碍环境建设法》于 2023 年施行,也明确将网站、移动互联网应用纳入信息无障碍的推进范围。不同法域的具体对象和执行强度不同,但大方向一致:数字产品负责任的时代已经来了。
| 法域 / 标准 | 主要对象 | 与 AI 相关的信号 |
|---|---|---|
| WCAG / W3C 无障碍指南 | 网站、Web 应用 | 强调可感知、可操作、可理解、健壮性,是自动化测试的重要依据 |
| 美国 ADA 及相关诉讼 | 公共场所、商业网站、App | 网站不可访问被起诉的案例增多,AI 客服、AI 对话也开始被讨论 |
| 欧盟《欧洲无障碍法案》 | 产品与服务 | 2025 年起逐步进入更强约束阶段 |
| 中国《无障碍环境建设法》 | 政务、公共生活、信息无障碍 | 已从物理设施延伸到网站和移动应用,相关配套技术要求还会细化 |
需要说明,上面的对比只是帮助理解趋势,不是法律意见。真实的合规判断必须结合具体业务、法域和最新政策,必要时应咨询专业法律人士。
不过如果往前多看半步,真正的争议点在于:传统无障碍法律要求的是“让既有产品可被访问”,而不是“保证每个公民都能使用某一种新技术”。AI 作为新一代通用能力,是不是一种应当被保障的基础资源?这个问题在法律上一时半会很难达成共识,但它给工程侧的启发已经很清晰了:做 AI 应用的时候,不能只把残障用户当作“测试的一小类人群”,而要把他们当成默认用户来设计。
3. 为什么“写进法律”没那么简单:四个技术断点
如果只是谈理念,“残障用户需要 AI”几乎没有人反对。但从法律条文走向技术验收,中间隔着四个很实际的断点。理解这些断点,能解释为什么立法迟迟没有跟上,也能告诉你研发侧应该在哪里使劲。
第一个是“判定断点”。什么叫“有权使用 AI”?是指每个人都有权调用某一个公开大模型的 API,还是指当服务商把 AI 作为主要客服入口时,必须为无法使用 AI 的人保留人工通道?两者的差异极大。法律条文通常只能写原则,但 AI 模型、版本、价格、接口形态变化太快,原则很难落地成一条可执行的验收标准。更实际的做法是倒过来:不问“谁有权用 AI”,而是问“一个残障用户在这条服务链路上,能不能完成和非残障用户等价的动作”。
第二个是“验证断点”。传统无障碍已经有 WCAG 之类的可测试标准,自动化工具可以检查按钮是否有可访问名称、图片是否有替代文本、颜色对比度是否达标。但 AI 输出的正确性很难自动化验证。你无法用一个脚本断言“这次对图片的描述就一定是对的”,因为模型是概率性的。你可以测试界面层“是否能聚焦、是否可点击”,但测试不了“这次回答对这个用户是否有价值”。所以面向无障碍的 AI 系统,除了常规单测,还需要一套持续维护的评测集,把真实场景里的图片、文本、方言语音都放进去,每次模型升级都要跑一遍。
第三个是“质量断点”。无障碍场景对 AI 错误更敏感。普通用户遇到模型幻觉,笑一笑就过去了;如果图片描述服务把一个红灯描述成“绿灯”,盲人用户很可能因此做出错误判断。这要求 AI 产品在输出侧做额外的安全设计,例如给出置信度提示、主动说明“图片某个区域模糊无法判断”,以及提供人工复核渠道。模型本身达不到完美并不可怕,可怕的是把不确定的内容包装成确定的事实。
第四个是“责任断点”。当 AI 成为辅助技术的一部分后,一旦出错,谁负责?是模型提供方、应用开发方,还是部署运维方?假如一个视障用户因为 AI 的错误描述错过了重要信息,他应该找谁?这个责任链条在传统软件里比较清晰,在 AI 链路里则复杂得多。很多厂商担心的正是这种“兜底责任”不确定,才不敢把无障碍 AI 当成正式承诺。
这四个断点也是工程机会。我们无法替立法者决定规则,但可以先建立技术上的可观测、可评测、可回滚机制。当一套 AI 功能既能产出结构化结果、又能给出不确定性说明、还能被读屏软件正常播报时,它至少已经满足了未来合规的底层条件。
4. AI 应用的可访问性架构:从“产品无障碍”到“无障碍 AI 服务”
把前面的讨论落到系统设计上,可以把一个 AI 应用拆成三层:输入层、模型层、输出层。每一层都有独立的可访问性问题,团队的测试方法也应该分层做。
输入层解决“用户怎么把问题交给 AI”。一个只有鼠标操作的大画布交互界面,对键盘用户就是灾难;一个只接受文字输入、不支持语音的服务,对部分运动障碍用户也很不友好。做输入层时,至少要保证所有操作都能用键盘完成,表单控件有清晰的 label,错误提示不用颜色作为唯一标识,语音输入功能在嘈杂场景下有文字纠错入口。这里真正容易踩坑的地方是“语音输入”看似无障碍,实际只对普通话标准、环境安静的用户友好,遇到方言、口音、语速变化时,识别率会断崖式下降。所以不要把语音当成唯一的无障碍输入方案,要保留文字纠错和备用通道。
模型层解决“AI 对不同用户的识别与理解是否公平”。常见问题包括语音模型对特定口音识别率低、视觉模型对少数族裔或非典型场景描述准确率偏低、文本模型生成内容默认使用复杂书面语而不是通俗语言。这一层需要用评测集来约束,把残障场景视为一等公民纳入模型评估,而不是只在发布后由用户反馈。
输出层是最容易被忽视的环节,也是工程改动成本最低、收益最高的环节。AI 输出的常常是 Markdown、长段落、表格、甚至一整个流式文本流。读屏软件无法理解光标乱跳的流式更新,也无法把“#”和“**”读成结构化语义。如果前端直接把模型返回的原始 Markdown 文本塞进直播区域,读屏用户听到的很可能是一堆符号噪声。正确的做法是让模型返回结构化 JSON,把“短摘要”“详细正文”“不确定性说明”分开,前端再把正文渲染成真实的语义 HTML,而不是让用户直接接触原始文本。
一个比较好的落地模式,可以概括为“三个出口”:机器可读的 JSON 出口,给开发者和其他系统;语义化 HTML 出口,给读屏软件和正常浏览器;纯文本摘要出口,给低视力用户或极简界面。也就是说,AI 应用的接口设计要从“返回一段字符串”升级为“返回一文一档、多版本可访问内容”。
5. 实操示例:搭建一个“无障碍优先”的 AI 图片描述服务
这里用一个最小可运行的示例,演示 AI 输出层的结构化设计。
需求场景很常见:用户上传一张截图或图片,AI 返回三个字段:
alt_text:一句话,适合直接作为<img>的 alt 属性;full_description:详细描述,供读屏用户展开阅读;confidence_note:对图片中无法确定部分的说明,降低误导风险。
先准备项目目录和依赖。
mkdir a11y-ai-demo cd a11y-ai-demo touch app.py requirements.txt .env.examplerequirements.txt内容如下,具体版本以实际项目为准:
fastapi>=0.100,<1.0 uvicorn[standard]>=0.23,<1.0 openai>=1.0,<2.0 pydantic>=2.0,<3.0.env.example文件用于提示环境变量:
# 复制为 .env 使用,真实密钥绝不能提交到 Git OPENAI_API_KEY=your_api_key_here # 如果你的模型服务提供 OpenAI 兼容接口,可在这里指定地址 # OPENAI_BASE_URL=https://api.example.com/v1然后在app.py中实现服务接口。
# 文件路径:a11y-ai-demo/app.py import json import os from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from openai import OpenAI from pydantic import BaseModel, Field # OpenAI SDK 会从 OPENAI_API_KEY 环境变量读取密钥 client = OpenAI() # 模型名称以你的项目实际使用的视觉模型为准 MODEL = os.getenv("VISION_MODEL", "gpt-4o-mini") app = FastAPI(title="a11y-ai-demo") # 允许本地前端页面跨域请求;生产环境应写成明确的域名白名单 app.add_middleware( CORSMiddleware, allow_origins=["http://127.0.0.1:3000", "http://localhost:3000"], allow_methods=["POST"], allow_headers=["Content-Type"], ) class ImageQuery(BaseModel): image_url: str = Field(description="待描述图片的公开 URL 或 base64 data URL") question: str = Field(default="请描述这张图片的内容", max_length=500) class AltTextResult(BaseModel): alt_text: str = Field(description="一句话替代文本,用于 img 的 alt") full_description: str = Field(description="详细描述,适合读屏用户展开阅读") confidence_note: str = Field(description="对不确定内容的说明,避免过度自信") def _build_messages(query: ImageQuery): return [ { "role": "system", "content": ( "你是图片无障碍描述助手。请输出 JSON,包含三个字段:" "alt_text(一句话,不超过 50 字)、" "full_description(3 到 8 句的详细描述)、" "confidence_note(说明图片里哪些区域无法确定)。" "不要把 JSON 之外的文字写入回答。" ), }, { "role": "user", "content": [ {"type": "text", "text": query.question}, {"type": "image_url", "image_url": {"url": query.image_url}}, ], }, ] def _describe_with_model(query: ImageQuery) -> dict: try: response = client.chat.completions.create( model=MODEL, messages=_build_messages(query), response_format={"type": "json_object"}, temperature=0.2, ) content = response.choices[0].message.content if not content: raise ValueError("模型返回内容为空") data = json.loads(content) return { "alt_text": data.get("alt_text", ""), "full_description": data.get("full_description", ""), "confidence_note": data.get("confidence_note", ""), } except json.JSONDecodeError: raise HTTPException(status_code=502, detail="模型输出不是合法 JSON,请重试") except Exception: # 生产环境应把原始异常记入日志,不要把内部细节直接返回给客户端 raise HTTPException(status_code=502, detail="图片描述服务暂时不可用,请稍后重试") @app.post("/api/v1/describe-image", response_model=AltTextResult) async def describe_image(query: ImageQuery): if not query.image_url.startswith(("http://", "https