资讯动态

将 Sanity stale-content-digest 定时函数适配到自有 Schema:从 LLM 提示词到蓝图部署的完整实战

发布时间:2026/9/18 12:22:04 来源:尧图企业网站定制
将 Sanity stale-content-digest 定时函数适配到自有 Schema从 LLM 提示词到蓝图部署的完整实战【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity本指南以examples/functions/stale-content-digest示例中的 LLM 适配提示词PROMPT.md为核心讲解如何把一份周一定时扫描过时内容 → 交给 Sanity Content Agent 审查 → 推送 Slack 摘要的 Sanity Function 配方从内置的movie演示模型改造成你自己的内容模型。读完你不仅能跑通两条接入路径moviedb 演示与已有项目还能掌握 GROQ 查询改写、TypeScript 接口同步、Agent 指令定制这三大改造点的底层原理并完成本地测试与生产部署。一、这个函数解决什么问题内容会悄悄过时引用过期、表述失真、链接失效编辑团队往往要等读者投诉才发现。人工巡检内容新鲜度既枯燥又难以持续。stale-content-digest正是针对这一场景的调度函数配方——每个周一早上它扫描数据集中长时间未更新的文档把摘要交给 Sanity Content Agent 审查再把过时清单推送到 Slack。它把 Sanity 的三项能力打包进一条工作流Scheduled Functions定时函数服务端 cron无需自建基础设施Agent Actions代理动作Content Agent 逐篇阅读文档并说明哪里看起来过时Slack 集成结论直接落到编辑团队日常所在的频道里。从 README.md 的定位看收益是及早发现漂移、每周仅一条低噪音消息、AI 代替人读完全部候选文档、零基础设施。二、前提条件Node.js v22.x启用了Functions和Agent Actions的 Sanity 账号。注意 Agent Actions 属于实验特性运行在vXAPI 通道源码中API_VERSION vX即为此注释见 index.tsSanity CLInpx sanity即可无需全局安装一个可以安装 bot 的 Slack 工作区需要管理员或应用安装权限。三、快速启动两条接入路径根据你是否已有 Sanity 项目选择两条路径之一两者共用下面的 Slack App 配置小节。路径 A用 moviedb 示例快速演示配方出厂时预接moviedbstarter一个带 Portable Textoverview字段的movie文档类型以及一份真实电影样例数据集。mkdir movies-project cd movies-project npx sanity init --template moviedb --import-dataset --output-pathstudioCLI 会依次询问组织、项目名和数据集。--output-pathstudio会把 Studio 代码放到./studiomovies-project/成为项目根目录后续命令都在此执行。接着初始化蓝图并添加配方npx sanity blueprints init npx sanity blueprints add function --example stale-content-digest第二条命令会把函数代码和一个半成品蓝图资源脚手架到functions/stale-content-digest/下一步需要手动补全蓝图。随后用下面的 蓝图参考配置 替换sanity.blueprint.ts并创建.envPROJECT_IDyour-sanity-project-id DATASETproduction SLACK_OAUTH_TOKENxoxb-your-bot-token SLACK_CHANNEL#your-test-channel DAYS_SINCE0PROJECT_ID可在studio/sanity.config.ts中找到或运行npx sanity projects list。DAYS_SINCE0会让所有电影都够格算作过时保证首次运行一定有输出。安装依赖——注意这里有两层项目根需要dotenv蓝图靠它加载环境变量moviedb starter 不自带而函数目录有自己独立的package.json内含sanity/client、sanity/functions、slack/web-api项目根的安装不会触及它npm install dotenv cd functions/stale-content-digest npm install cd ../..本地跑一次npx sanity functions test stale-content-digest --dataset production --with-user-token--with-user-token会把已登录 Sanity CLI 配置里的认证注入运行时让函数内的sanity/client能读取数据集。如果一切正常Slack 里会收到一份对各电影 overview 的毒舌点评。路径 B接入你已有的 Sanity 项目已有 Studio 和待审内容时走这条路径。在包含或希望存放Blueprint 配置的目录打开终端通常与 Studio 配置同级或上一级。然后完成下方 Slack App 设置若无蓝图则npx sanity blueprints initnpx sanity blueprints add function --example stale-content-digest打开sanity.blueprint.ts按 蓝图参考配置 更新若已有其他资源把defineRobotToken和defineScheduledFunction加进现有resources数组适配函数到你的 Schema本指南核心见 第六节创建或追加.envPROJECT_IDyour-sanity-project-id DATASETproduction SLACK_OAUTH_TOKENxoxb-your-bot-token SLACK_CHANNEL#content-team DAYS_SINCE180 STUDIO_URLhttps://your-studio.sanity.studioSTUDIO_URL可选设置后每条 Slack 发现都会带上打开对应文档的 Studio 深链。安装依赖同上两条命令本地运行同上npx sanity functions test命令。交互式迭代可把test换成dev若数据集太新导致查不出结果可临时把DAYS_SINCE调成0。四、蓝图配置与环境变量配方完整的sanity.blueprint.ts参考如下来自 README.md 的 Blueprint reference 小节import {defineBlueprint, defineRobotToken, defineScheduledFunction} from sanity/blueprints import dotenv/config import {env} from node:process const {PROJECT_ID, DATASET, DAYS_SINCE, SLACK_OAUTH_TOKEN, SLACK_CHANNEL, STUDIO_URL} env export default defineBlueprint({ resources: [ defineRobotToken({ name: stale-content-digest-robot, label: Stale Content Digest Robot, memberships: [ { resourceType: project, resourceId: PROJECT_ID, roleNames: [viewer], }, ], }), defineScheduledFunction({ name: stale-content-digest, src: ./functions/stale-content-digest, event: {expression: 0 8 * * 1}, timezone: UTC, memory: 1, timeout: 30, env: { PROJECT_ID, DATASET, DAYS_SINCE, SLACK_OAUTH_TOKEN, SLACK_CHANNEL, STUDIO_URL, }, robotToken: $.resources.stale-content-digest-robot.token, }), ], })要点robot token 被授予项目viewer角色只读即可完成查询cron 表达式0 8 * * 1表示每周一 08:00 UTC 运行robotToken: $.resources.stale-content-digest-robot.token是 JSONPath 引用把 robot token 注入函数运行时环境。函数目录下的 package.json 也内嵌了一份等价的blueprintResourceItem描述memory、timeout、cron、env 透传清单供blueprints add脚手架使用。环境变量一览变量说明必填PROJECT_IDSanity 项目 ID。蓝图 robot token 使用它函数客户端也直接读取定时函数处理器不会通过context.clientOptions拿到projectId。可在studio/sanity.config.ts或npx sanity projects list中找到是DATASET要查询的数据集如production由函数客户端直接读取是SLACK_OAUTH_TOKENSlack bot tokenxoxb-...是SLACK_CHANNEL频道名如#content-team是DAYS_SINCE过时阈值天数默认180否STUDIO_URLStudio 基础 URL设置后每条发现会附带文档深链否NOTIFY_WHEN_EMPTY设为true时即使没有发现也会发一条无过时内容消息默认false否SANITY_AUTH_TOKEN函数客户端用 API token。仅在不使用--with-user-token、且尚未部署 robot token 的场景如直接node调用才需要否五、Slack App 设置需要一个可投递摘要的 bot token在 Slack 应用管理页面点击Create New App → From scratch命名并选择工作区在OAuth Permissions → Bot Token Scopes中添加chat:write权限点击Install to Workspace完成安装复制 Bot User OAuth Token以xoxb-开头作为SLACK_OAUTH_TOKEN在摘要要落地的频道里运行/invite your-app-name把该频道名带#填入SLACK_CHANNEL。六、核心用 PROMPT 把函数适配到你的 Schema这是整个配方的灵魂所在。函数出厂指向movie文档类型和单个 Portable Textoverview字段而你的内容模型几乎必然不同。适配有两种姿势源码层面都收敛到 PROMPT.md 描述的步骤上编码 Agent推荐在项目根目录打开 Claude Code、Cursor 或 Codex直接说adapt functions/stale-content-digest/ to my schema。函数目录自带一份 AGENTS.md编码 Agent 会自动发现它并遵循其中指引先读 schema再改 GROQ 查询、TypeScript 接口与 Agent 指令。若多个类型都符合条件Agent 会反问你要定位哪个聊天式 LLM把 PROMPT.md 整份粘贴进 ChatGPT 或 Claude.ai。最好配合 Sanity MCP 使用让 LLM 能直接读你的 schema对应list_workspace_schemas与get_schema工具手改按 README 的 Customization 小节改 index.ts 的三处。无论哪种方式PROMPT 的核心流程都是先填两个占位符再走四步。占位符是TARGET DOCUMENT TYPE: fill in, e.g. documentation TARGET FIELDS: fill in one or more comma-separated fields to review for staleness, e.g. body or title, lead, body步骤 1先验证目标不要凭空发明确认你点名的文档类型和每个字段确实存在于 Sanity schema 中。优先用 Sanity MCP 的list_workspace_schemas和get_schema否则直接读 schema 文件通常在schemaTypes/或schemas/下。对每个字段记下它的_typestring、text、Portable Text 数组、字符串数组等——GROQ 投影怎么写取决于它。如果类型或任一字段不存在停下来请用户纠正而不是自行臆造字段名。这一点在 AGENTS.md 中被反复强调不要发明文档类型或字段名编辑前必须确认它们真实存在。步骤 2改 index.ts 的三处README 的 Customization 同源a. GROQ 查询STALE_QUERY常量见 index.ts。把_type movie换成你的类型投影出标题等价字段和每个目标字段。投影规则按字段类型决定字段类型GROQ 投影写法普通 string / textmyField或重命名Portable Text 数组myField: pt::text(myField)字符串数组myField: array::join(myField, )其他对象、引用等停下来问用户希望如何展平为纯文本如果你给了多个字段每个字段都要在 GROQ 里单独投影到自己的键名下不要预先拼接——Agent 提示词和 TypeScript 接口会分别引用它们。b. TypeScript 接口文件顶部的Movie见 index.ts。重命名它并把字段类型改成与查询返回值一致。c. Agent 指令提示词传给 agent action 的instruction字段见 index.ts。把毒舌影评人人设替换成贴合你内容类型的角色并且要明确写出你所在领域的过时长什么样——比如文档类是过时的 API 引用、营销内容类已失效的促销、资源页类断裂的链接。JSON 输出 schema 保持不变instruction: You are a content auditor. Review these articles: $documents. Flag overviews with outdated references, broken claims, or missing recent context. Respond in JSON: { findings: [{ title: ..., issue: ..., priority: high|medium|low }] }步骤 3更新用户可见文案index.ts里的日志消息和 Slack 消息头把 stale movies 换成 stale [你的内容类型]。对应源码中的Found ${stale.length} stale moviesindex.ts与*${findings.length} stale movies need attention*index.ts等处。步骤 4更新 Studio 深链如果文档类型的 slug 与movie不同需要改formatSlackMessage里的深链路径——源码中是${STUDIO_URL}/structure/movie;${id}|open in Studioindex.tsmovie即为文档类型 slug。完成后让 Agent 展示 diff并让它根据你的数据集给出首个可用的DAYS_SINCE建议。七、源码级拆解函数内部如何工作要真正理解这三处改动值得通读 index.ts 的完整执行链路入口export const handler scheduledEventHandler(async ({context}) {...})index.ts。scheduledEventHandler来自sanity/functions把函数包装成定时事件处理器客户端构造与文档事件处理器不同定时处理器不会从context.clientOptions拿到projectId/dataset只有 token。因此源码特意从 env经蓝图env块透传读取项目与数据集再从context.clientOptions?.token ?? SANITY_AUTH_TOKEN取 tokenindex.ts。这解释了为什么PROJECT_ID在环境变量表中是必填项也对应 README 排错第一条Configuration must contain projectId查询client.fetchMovie[](STALE_QUERY, {daysSince})执行 GROQdateTime(_updatedAt) dateTime(now()) - 60*60*24*$daysSince实现N 天未更新过滤空结果分支无过期文档时除非NOTIFY_WHEN_EMPTY true否则直接 return 不发 Slackindex.tsAgent 调用client.agent.action.prompt({instruction, instructionParams: {documents: JSON.stringify(stale)}, format: json})index.ts——$documents是模板变量被替换成序列化后的过时文档数组format: json要求结构化输出与Analysis接口{findings: Finding[]}对应消息格式化formatSlackMessage用标题到_id的 Map 把 Agent 返回的 finding 映射回文档并依STUDIO_URL决定是否生成深链index.ts发送new WebClient(SLACK_OAUTH_TOKEN).chat.postMessage({channel: SLACK_CHANNEL, text})index.ts。八、自定义与调优1. 把查询指向你的文档类型const STALE_QUERY *[_type post dateTime(_updatedAt) dateTime(now()) - 60*60*24*$daysSince]{ _id, title, _updatedAt, body: pt::text(body) }投影按字段类型选择同上表普通字符串直接投影Portable Text 用pt::text字符串数组用array::join更复杂的结构对象、引用在投影里先展平成字符串让 Agent 读到的永远是纯文本。2. 重写提示词贴合你的内容毒舌人设只是演示趣味。真实编辑场景应点名团队关心的具体过时形态并给出明确的 JSON 输出示例见第六节步骤 2c 的示例。收紧 JSON schema、给出期望输出样例也是排错中Agent 返回非法 JSON的标准解法。3. 调整调度改蓝图里的 cron 表达式即可event: { expression: 0 9 * * 1-5 } // every weekday at 09:00其他旋钮多类型把_type movie换成_type in [post, page, guide]换过时信号把_updatedAt换成自定义字段如lastReviewedAt更丰富的 Slack 排版把chat.postMessage换成 Block Kit 块。九、部署到生产本地测试无需部署即可验证逻辑要让函数真正按计划跑起来需要部署。关键认知定时函数位于组织级organization-scopedBlueprint而非项目级。首次部署前要先提升栈npx sanity blueprints promote提升是单向的已有项目级资源保持项目级新的定时函数落到组织级。随后npx sanity blueprints plan # 可选预览将被创建/更新/删除的资源 npx sanity blueprints deployblueprints plan是干跑不会改动栈。CI 里部署组织级栈目前需要个人用户 token不适合共享 CI runner因此在平台支持自动化组织级部署前请从开发者本机部署。十、本地测试与排错迭代时在test与dev间切换test跑单次dev提供交互式开发模式。常见问题速查详见 README.md 的 Troubleshooting 小节症状原因解法Configuration must contain projectId定时处理器不从context.clientOptions拿projectId而 env 中缺失或未加载PROJECT_ID确认.env有PROJECT_ID且蓝图函数env块透传了它有内容却没查出staleDAYS_SINCE对数据集年龄过大或 GROQ 过滤器不匹配类型调低DAYS_SINCE首次可试0核对类型与字段名Slack 收不到消息bot 不在目标频道或 token 缺chat:write在频道/invite your-app-name在 Slack 应用 OAuth 页核对 scopeAgent 返回非法 JSON提示词对模型太开放收紧 instruction 中的 JSON schema给出期望输出示例函数超时过期文档太多Agent 调用是瓶颈提高蓝图timeout、分批处理文档或收窄查询十一、相关示例同目录下还有可交叉参考的配方Auto-Summary 用 Content Agent 在每次更新时生成摘要展示了agent.action.generate配合target/documentId写回文档的用法Slack Notify 演示文档创建时推送 SlackStale Products Analysis 则是文档触发式的电商内容新鲜度分析展示了更复杂的嵌套 GROQ 投影modules网格内的产品引用展平与不同的过时判定逻辑可作为把本配方扩展到复杂模型的参考。【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价