news 2026/9/19 5:11:03

Sanity Functions + Agent Actions:媒体库多语言 Alt Text 自动生成实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sanity Functions + Agent Actions:媒体库多语言 Alt Text 自动生成实战指南

Sanity Functions + Agent Actions:媒体库多语言 Alt Text 自动生成实战指南

【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity

本文围绕 Sanity 官方示例media-library-auto-alt-text展开,讲解如何基于 Sanity Functions(服务端无服务器函数)与 Agent Actions,在 Media Library 资源创建或更新时自动为图片生成多语言无障碍 alt text(替代文本)。读完本文,你将掌握 Blueprint 函数的配置方法、Aspect 数据模型、Agent Action 的调用方式,以及 Asset Container 与 Image Asset 的底层数据解构与 GROQ 查询技巧,并能在自己的 Sanity 项目中直接复刻这套能力。

背景:为什么需要自动化的多语言 Alt Text

内容团队需要为图片提供无障碍 alt text,以支撑屏幕阅读器等辅助技术。然而在全球化场景下,手动为每一个资源逐一撰写多语言 alt text 既耗时又难以保持一致——不同成员、不同语言产出的描述质量参差不齐,最终造成可访问性缺口和糟糕的用户体验。

media-library-auto-alt-text正是为解决该痛点设计的示例函数:当资源在 Media Library 中被创建或更新时,函数会等待 Sanity 自动生成的关键词(keywords)就绪,然后调用 Agent Actions 为每个目标语言生成简洁、描述性的 alt text,最后将结果写入资源的aspects(资源自定义元数据)中。整套流程无需人工干预,即可实现 "Accessibility by default"。

该示例的核心收益包括:

  • 默认可访问:每个图片自动获得描述性 alt text;
  • 多语言支持:一次上传同时生成多种语言的 alt text;
  • 提示词可定制:可调整 AI 提示词以匹配品牌语气与需求;
  • 结果可编辑:生成的 alt text 可在 Media Library UI 中直接审阅修改;
  • 节省时间:消除手动编写 alt text 的重复劳动,同时保持质量与一致性。

适用范围与前置概念

该函数面向任何启用了 Media Library 的 Sanity 项目,它直接作用于 Media Library 中的资源,不依赖特定的 Studio 模板,因此开箱即用。

在深入实现之前,需要先理解两个核心概念。

理解 Aspects(资源自定义元数据)

Aspects 是作用于资源(asset)的 schema 风格字段,用于为资源附加额外的元数据,帮助组织和标识资源。关键要点:

  • Aspects 由开发者定义,由系统或用户应用到资源上;
  • Aspects 存储在每个资源的aspects对象中。

本函数包含一个 Aspect:altText,它定义了多语言 alt text 的结构与编辑 UI。其完整定义见 altText.ts:

import {defineAssetAspect, defineField, defineArrayMember} from 'sanity' const languages = [ {title: 'Dutch', value: 'nl'}, {title: 'English', value: 'en'}, {title: 'French', value: 'fr'}, {title: 'German', value: 'de'}, ] export default defineAssetAspect({ name: 'altText', title: 'Alternative text', description: 'Accessible alternative text for this asset, in one or more languages.', type: 'array', of: [ defineArrayMember({ name: 'altTextItem', type: 'object', fields: [ defineField({ name: 'language', type: 'string', description: 'The language that the alt text is written in', options: { list: languages, layout: 'radio', }, }), defineField({ name: 'value', title: 'Alternative text', type: 'string', description: 'Short description of the image, for screen readers (max ~100 characters).', }), ], preview: { select: { title: 'value', subtitle: 'language', }, }, }), ], })

从结构上看,这是一个数组类型的 Aspect:数组元素altTextItem是一个对象,包含language(下拉/单选列表,限定四种语言)与value(alt text 内容,提示约 100 字符内)两个字段,并配置了以value为标题、language为副标题的预览。

关于defineAssetAspect的底层机制,可参考其类型定义源码 defineAssetAspect.ts:它会基于传入的定义生成一个_type为 Media Library asset aspect 类型的文档对象,_idnameassetType会被规范化为数组,public字段透传。该文件注释明确指出Aspects 可通过sanity media deploy-aspectCLI 命令部署。仓库中还提供了另外两个 Aspect 示例可供参考:colourDetails.ts(颜色信息)与 productDetails.ts(价格与库存),它们展示了用相同 API 定义不同类型 Aspect 的写法。

前提要求

  • Media Library:函数作用于 Media Library 中的资源;
  • Media Library Aspect Setup:需要先完成 Aspect 的配置部署。

快速开始:初始化与添加函数

注意:以下命令必须从项目根目录运行(不要进入studio/子目录)。

  1. 初始化 Blueprint 示例

    如果尚未初始化 blueprints,先执行:

    npx sanity blueprints init

    执行时系统会提示选择你的组织(organization)和 Sanity Studio。

    随后添加函数:

    npx sanity blueprints add function --example media-library-auto-alt-text
  2. 找到你的 Media Library ID

    配置函数需要 Media Library ID。打开 Media Library,从 URL 中获取:

    https://www.sanity.io/@<organizationId>/media/<mediaLibraryId>/assets

    <mediaLibraryId>这一段。

  3. 在 Blueprint 中添加配置

    编辑项目根目录的sanity.blueprint.ts

    // sanity.blueprint.ts import {defineBlueprint, defineMediaLibraryAssetFunction} from '@sanity/blueprints' export default defineBlueprint({ resources: [ defineMediaLibraryAssetFunction({ name: 'media-library-auto-alt-text', memory: 2, timeout: 30, src: './functions/media-library-auto-alt-text', event: { on: ['create', 'update'], filter: 'assetType == "sanity.imageAsset" && !defined(aspects.altText)', projection: '{ _id, currentVersion }', resource: { type: 'media-library', id: '<your-media-library-id>', // TODO: replace with your media library id }, }, }), ], })

    其中id字段需要替换为上一步获取到的真实 Media Library ID。

  4. 安装依赖

    在项目根目录与函数目录分别安装依赖:

    # Install in the functions directory cd functions/ pnpm install

配置参数详解

defineMediaLibraryAssetFunction的各个配置项与函数package.json中的blueprintResourceItem(见 package.json)一一对应:

参数示例值作用
namemedia-library-auto-alt-text函数唯一标识
memory2函数运行内存(GB)
timeout30单次执行超时时间(秒)
src./functions/media-library-auto-alt-text函数代码目录
event.on['create', 'update']触发时机:资源创建与更新时激活
event.filterassetType == "sanity.imageAsset" && !defined(aspects.altText)事件过滤条件:仅处理图片资源,且尚未生成 altText(同时承担防循环职责)
event.projection{ _id, currentVersion }事件载荷投影,最小化传入函数的数据
event.resource.typemedia-library资源类型:Media Library
event.resource.id<your-media-library-id>目标 Media Library ID

仓库根目录的 sanity.blueprint.ts 展示了另一种组织方式:通过遍历functions/目录读取各示例package.json中的blueprintResourceItem,按资源类型分发到defineMediaLibraryAssetFunctiondefineDocumentFunction,实现函数资源的自动发现与注册——如果你有大量函数,可以采用这种"按包自描述"的注册模式。

函数核心实现解读

函数主逻辑位于 index.ts。它基于@sanity/functionsdocumentEventHandler@sanity/client,核心依赖见 package.json:@sanity/client(^8.6.1)与@sanity/functions(^1.7.2)。

等待关键词就绪(waitForKeywords)

Media Library 会在图片上传后自动生成关键词,但该过程是异步的,因此函数必须轮询等待。源码中通过两个常量控制重试策略:

const MAX_KEYWORD_WAIT = 5 // How many times to retry (total attempts = MAX_KEYWORD_WAIT + 1) const KEYWORD_WAIT_MS = 1500 // Wait 1.5 seconds between checks

waitForKeywords最多执行 6 次检查(5 次重试 + 1 次初始尝试),每次间隔 1.5 秒;一旦拿到非空关键词数组立即返回,否则最终返回undefined并跳过本次执行。

创建 Media Library 客户端并拉取关键词

const mediaLibraryClient = createClient({ token: context.clientOptions.token, useCdn: false, apiVersion: '2025-05-08', resource: { type: 'media-library', id: mlId, }, }) const fetchKeywords = async () => { try { const result = await mediaLibraryClient.fetch<{keywords?: string[]}>( `*[_id == $assetId][0]{ "keywords": metadata.keywords }`, {assetId: detailedAssetId}, ) return result?.keywords || [] } catch (err) { console.error('Failed fetching keywords from asset', err) return [] } }

注意这里的两个关键细节:

  • 客户端通过resource: {type: 'media-library', id: mlId}直接绑定 Media Library 资源,useCdn: false确保读到实时数据;
  • detailedAssetId来自事件载荷中currentVersion._ref,即底层图片文档的引用。函数显式处理了!detailedAssetId的情况,直接跳过执行。

调用 Agent Actions 逐语言生成 alt text

拿到关键词后,函数为每个语言分别发起一次 Agent Action 调用——逐个语言、串行生成,以保证可靠性:

const agentClient = createClient({ ...context.clientOptions, dataset: 'production', apiVersion: 'vX', }) const altTextItemsArray: {_key: string; _type: string; language: string; value: string}[] = [] for (const lang of languages) { const altText = await agentClient.agent.action.prompt({ instruction: `Given the following keywords: [${keywords.join(', ')}], generate a short (max 100 chars) alt text in language: ${lang}. Respond with just the alt text string, no quotes or formatting.`, }) altTextItemsArray.push({ _key: crypto.randomUUID(), _type: 'altTextItem', language: lang, value: String(altText).trim(), }) }

默认语言数组为const languages = ['nl', 'en', 'fr', 'de'](荷兰语、英语、法语、德语),与 Aspect 定义中的语言列表保持一致。Agent 提示词明确约束了输出格式:"不超过 100 字符、只返回 alt text 字符串、不加引号或格式",从而保证生成结果可直接入库。每个条目使用crypto.randomUUID()生成_key

写回 Aspects

最后,使用 Media Library 客户端将结果写入资源的aspects对象:

const result = await mediaLibraryClient .patch(_id) .setIfMissing({aspects: {}}) .set({'aspects.altText': altTextItemsArray}) .commit() console.log('Mutation response:', JSON.stringify(result, null, 2))

setIfMissing确保aspects对象存在,set则写入/覆盖altText数组。需要特别留意:如果资源已存在altText,事件过滤器(!defined(aspects.altText))会让函数直接跳过,这是防止"更新→再触发→再更新"死循环的关键机制

数据模型:Asset Container 与 Image Asset

Media Library 区分两个相互关联的实体,理解这一点是编写与调试相关函数的基础:

  • Asset Container:代表媒体条目的"主"文档,自定义 aspects 存储在这里;它包含对图片文档的引用;
  • Image Asset:实际的图片文件,携带系统级元数据(尺寸、EXIF、关键词等)。

数据存放位置速查:

  • 自定义 Aspects(altText)→ Asset Container 的aspects对象;
  • 自动生成的关键词 → Image Asset 的metadata.keywords数组(上传新图片时自动生成);
  • 图片尺寸、EXIF 数据 → Image Asset 的metadata对象。

要读取位于图片文档上的关键词等数据,需要对currentVersion引用进行解引用(dereference):

*[_id == $assetContainerId][0]{ ..., "metadata": currentVersion->{ metadata } }

Asset Container 结构

查询 Media Library 资源时(以下为脱敏示例),你将看到如下结构:

{ "_createdAt": "2025-01-01T12:00:00Z", "_id": "<asset-id>", "_rev": "<revision-id>", "_system": { "createdBy": "<user-id>" }, "_type": "sanity.asset", "_updatedAt": "2025-01-02T15:00:00Z", "aspects": { "altText": [ { "_key": "<key-1>", "_type": "altTextItem", "language": "nl", "value": "voorbeeld alt text" }, { "_key": "<key-2>", "_type": "altTextItem", "language": "en", "value": "Example alt text" } ] }, "assetType": "sanity.imageAsset", "cdnAccessPolicy": "public", "currentVersion": { "_ref": "<image-asset-version-ref>", "_type": "reference" }, "title": "example-image.png", "versions": [ { "_key": "<version-key-1>", "_type": "sanity.asset.version", "instance": { "_ref": "<image-asset-version-ref>", "_type": "reference" }, "title": "example-image.png" } ] }

关键点:

  • aspects对象承载你的自定义元数据(完全可定制);
  • currentVersion字段引用实际的图片文档;
  • versions数组追踪资源的所有版本(支持资源版本化)。

Image Asset 结构与元数据查询

实际的图片元数据位于图片文档上,而非 Asset Container。通过解引用currentVersion引用即可获取完整图片数据:

*[_id == $currentVersion._ref][0]

返回的底层图片结构(脱敏示例):

{ "_id": "<image-asset-version-id>", "_type": "sanity.imageAsset", "metadata": { "_type": "sanity.imageMetadata", "blurHash": "<blur hash string>", "dimensions": { "_type": "sanity.imageDimensions", "aspectRatio": 0.68, "height": 1200, "width": 810 }, "hasAlpha": false, "isOpaque": true, "keywords": ["movie poster", "character", "studio", "red cape"], "lqip": "<data-url>", "palette": { "_type": "sanity.imagePalette", "darkMuted": { "_type": "sanity.imagePaletteSwatch", "background": "#4c3134", "foreground": "#fff", "population": 0.36, "title": "#fff" } } }, "originalFilename": "<image-file-name>.jpg", "mimeType": "image/jpeg", "cdnAccessPolicy": "public" }

重要提示:

  • Media Library 生成的关键词位于图片文档的metadata.keywords
  • 为确保准确性,始终从解引用后的currentVersion读取图片元数据

这也解释了函数实现中的两层取数逻辑:事件载荷只携带{_id, currentVersion}投影,函数再用currentVersion._ref作为$assetId查询metadata.keywords——正是遵循了这一数据模型约定。

本地测试与部署

本地测试

在部署到生产环境之前,可以使用 Sanity CLI 在本地测试media-library-auto-alt-text函数的行为,确认触发逻辑、关键词等待与 Agent 生成链路均符合预期。

部署到生产

部署前置条件:项目已启用 Media Library。

1. 部署 Blueprint

从项目根目录运行:

npx sanity blueprints deploy

该命令会完成以下工作:

  • 打包你的函数代码;
  • 上传至 Sanity 的基础设施;
  • 配置资源创建/更新的事件触发器;
  • 让函数在生产环境生效。

2. 验证部署

部署完成后,可通过以下方式验证函数是否正常运行:

  • 向 Media Library 上传一个新资源;
  • 等待 Media Library 的 alt text 生成(通常只需几秒钟);
  • 检查资源的 aspects 中是否出现生成的 alt text;
  • 在 Sanity CLI 中监控函数日志。

自定义语言

如需增删语言,修改函数代码 index.ts:

  1. 更新languages数组,写入你需要的语言代码(如['en', 'ja', 'es']),并同步调整 Aspect 定义 altText.ts 中的语言选项列表,保持两端一致;
  2. 重新部署函数。

编辑已生成的 Alt Text

生成的 alt text 可直接在 Media Library UI 中编辑:

  1. 在 Media Library 中打开资源;
  2. 进入 Aspects 面板;
  3. 编辑任意语言的 alt text;
  4. 修改会立即保存。

故障排查

常见问题

报错:"No Media Library keywords found"

  • 原因:Sanity 的机器学习尚未生成关键词,或图片类型不受支持;
  • 解决:上传后等待几秒,函数内置了重试逻辑;同时确认资源是 Media Library 可处理的图片类型。

函数陷入循环触发

  • 原因:函数更新资源时又触发了自身;
  • 解决:函数通过检查是否已存在altText实现防循环逻辑(对应事件过滤器!defined(aspects.altText)),请确保不要移除这段逻辑。

生成的 alt text 不贴切

  • 原因:Media Library 关键词未能准确描述图片,或 AI 提示词需要调整;
  • 解决:在 Media Library UI 中手动编辑 alt text,或调整函数代码中的提示词以更贴合你的需求。

最佳实践

结合官方 README 与源码实现,可将以下实践沉淀为通用准则:

  1. 从图片资源(Image Asset)读取 Media Library 关键词,而不是从 Asset Container 读取;
  2. 将自定义元数据存放在 aspects(位于 Asset Container 上);
  3. 更新前检查是否已存在 Aspect 数据,防止更新循环(本示例通过事件过滤器与函数内双重防护实现);
  4. 对异步生成的数据保持耐心轮询:关键词由 Media Library 后台生成,函数需具备重试机制(MAX_KEYWORD_WAIT+KEYWORD_WAIT_MS);
  5. 逐语言串行调用 Agent Action:将多语言生成拆分为独立的、串行的 Agent 调用,比单次多语言请求更可靠;
  6. 严格约束 Agent 输出格式:在提示词中限定长度(如 max 100 chars)、禁止引号与多余格式,保证结果可直接入库渲染。

这一套"事件触发 → 等待异步数据 → Agent 生成 → 写回 Aspects"的链路,也可以平移到自动打标签、自动摘要、品牌语气校验等其他内容自动化场景——examples/functions 目录下的其他示例(如 auto-tag、auto-summary、brand-voice-validator)正是同一架构的不同应用,可作为扩展参考。

【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 5:19:09

Agent OS 安全工具开发实战:基于 ATR 构建可复用的 Custom Tools

Agent OS 安全工具开发实战&#xff1a;基于 ATR 构建可复用的 Custom Tools 【免费下载链接】agent-governance-toolkit AI Agent Governance Toolkit — Policy enforcement, zero-trust identity, execution sandboxing, and reliability engineering for autonomous AI age…

作者头像 李华
网站建设 2026/9/19 5:07:18

小说转漫画视频的多模态AI流水线实战指南

1. 这不是“一键成片”&#xff0c;而是多模态流水线的精密协同最近在几个小说创作群和AI工具交流圈里&#xff0c;反复看到有人发截图&#xff1a;一段《诡秘之主》的文本粘贴进去&#xff0c;3分钟生成带分镜、角色动作、配音和字幕的15秒漫画视频&#xff0c;评论区全是“求…

作者头像 李华
网站建设 2026/9/19 5:08:00

2026降AI率工具实测:从检测原理到9款工具横评

2026年了&#xff0c;还有人在问“AI率怎么降”这种问题&#xff0c;我其实挺意外的。更意外的是&#xff0c;现在网上一搜“降AI率工具”&#xff0c;跳出来的结果十个里有八个是割韭菜的&#xff0c;要么挂着免费引流然后强制付费&#xff0c;要么本身就是AI生成的垃圾站&…

作者头像 李华
网站建设 2026/9/19 5:18:33

Ubuntu黑屏怎么修?图形界面故障排查与急救完整指南

做了这么多年Linux系统运维和日常使用&#xff0c;隔三差五就会碰到有人抱着电脑过来&#xff0c;说“Ubuntu开机黑屏了”“进不去桌面了”“昨天还好好的&#xff0c;今天就这样了”。说实话&#xff0c;图形界面起不来这件事&#xff0c;在Ubuntu里实在太常见了&#xff0c;常…

作者头像 李华
网站建设 2026/9/19 5:20:58

杂种优势遗传机制解析与多组学整合分析技术

1. 项目背景与核心挑战杂种优势&#xff08;Heterosis&#xff09;是现代农业育种的核心现象之一&#xff0c;指杂交后代在生长势、产量、抗逆性等方面显著优于双亲的现象。这种现象自20世纪初被广泛认知以来&#xff0c;已成为玉米、水稻等主要农作物增产的关键手段。然而&…

作者头像 李华
网站建设 2026/9/19 5:07:22

.NET Reactor 7.3:从混淆到防篡改的代码保护实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华