Sink Workers AI 功能详解:为短链接生成智能 Slug 与社交预览元数据
【免费下载链接】Sink⚡ A Simple / Speedy / Secure Link Shortener with Analytics, 100% run on Cloudflare.项目地址: https://gitcode.com/GitHub_Trending/si/Sink
本文基于 Sink 官方文档 docs/features/ai.md 展开,介绍 Sink 如何把 Cloudflare Workers AI 作为可选能力接入短链接服务:通过/api/link/ai与/api/link/og-ai两个端点,由模型读取目标页面内容后生成短代码(slug)建议和 OpenGraph 标题/描述。读完本文,你将掌握 Workers AI 绑定的开启方式、三个相关配置项(模型与提示词)的默认值和约束,以及端点在“页面抓取失败、模型调用失败、响应解析失败”三类异常下的降级路径,并能对照源码验证每个行为。
功能定位:完全可选,不绑定也不影响核心链路
Sink 是一个 100% 运行在 Cloudflare 上的短链接服务(Simple / Speedy / Secure Link Shortener with Analytics)。Workers AI 在 Sink 中只是增强项:
- 绑定了
AI时,创建/编辑链接的界面可以用 AI 推荐短代码和社交预览文案; - 没有绑定时,链接创建、跳转、分析等全部核心功能照常工作,只是 AI 端点会返回501(
AI not enabled)。
配置参考文档 中也将AIbinding 明确标注为 Optional:“Workers AI suggestions”,而必需的绑定只有 D1(DB)、KV(KV)。
开启方式:将 Workers AI 绑定为 AI
开启的唯一前提是把 Workers AI 以固定名称AI绑定到 Worker。仓库中的 wrangler.jsonc 展示了本地/部署共用的绑定声明:
"ai": { "binding": "AI", "remote": true }绑定建立后,运行时代码通过event.context.cloudflare.env.AI访问该能力。如果未绑定,两个 AI 端点在入口处即抛出 501:
// server/api/link/ai.get.ts const { AI } = cloudflare.env if (!AI) { throw createError({ status: 501, statusText: 'AI not enabled' }) }server/api/link/og-ai.get.ts 中有完全相同的检查逻辑。模型与提示词则通过环境变量覆盖,默认值定义在 nuxt.config.ts 的runtimeConfig中:
| 变量 | 默认值 | 用途 |
|---|---|---|
NUXT_AI_MODEL | @cf/qwen/qwen3-30b-a3b-fp8 | Workers AI 使用的模型 |
NUXT_AI_PROMPT | 内置 slug 提示词 | 短代码生成提示词,自定义时必须保留{slugRegex}占位符 |
NUXT_AI_OG_PROMPT | 内置 OG 提示词 | 社交预览(OpenGraph)提示词 |
内置的aiPrompt全文为:
You are a URL shortening assistant, please shorten the URL provided by the user into a SLUG. The SLUG information should be derived from the URL and page content (if provided). Do not make any assumptions beyond the given information. A SLUG is human-readable and should not exceed three words and can be validated using regular expressions {slugRegex} . Only the best one is returned, the format must be JSON reference {"slug": "example-slug"}
{slugRegex}占位符在请求时会用应用配置里的真实正则替换。这个正则在 app/app.config.ts 中定义:
slugRegex: /^[a-z0-9]+(?:-[a-z0-9]+)*$/i,也就是说,模型被要求产出的 slug 必须符合“小写字母数字段 + 连字符”的格式——这正是 Sink 全站校验短代码用的同一套正则(见 server/middleware/1.redirect.ts 的跳转校验),保证 AI 建议出来的 slug 与平台可接受的 slug 天然一致。若自定义NUXT_AI_PROMPT时删掉该占位符,模型就无法感知格式约束,建议的 slug 更可能被后续规范化逻辑改写或不符合预期,这就是文档强调“必须保留{slugRegex}”的原因。
端点一:/api/link/ai —— 短代码建议
接口定义在 server/api/link/ai.get.ts,OpenAPI 元数据声明它“Generate a slug using AI based on the URL”,需要 Bearer 认证(即站点登录 token),必填 query 参数为url。
请求与参数
GET /api/link/ai?url=<目标链接>url由 zod 的z.url()校验:缺失或非法均返回400(测试用例见 tests/api/link.spec.ts)。
内部流程
- 抓取页面内容:调用
fetchPageMarkdown(event, url, AI)尝试读取目标页(详见下一节),成功则拼出URL: ...\n\nPage content: ...作为用户消息;失败则只用裸 URL。 - 构造 few-shot 对话:系统提示词为替换过
{slugRegex}的aiPrompt,随后内置 4 组示例对话(cloudflare→{"slug": "cloudflare"}、nuxt→{"slug": "nuxt"}、sink.cool→{"slug": "sink-cool"}等),最后追加当前目标。完整构造见 server/api/link/ai.get.ts。 - 调用模型:
AI.run(aiModel, { messages, chat_template_kwargs: { enable_thinking: false, thinking: false } }),通过chat_template_kwargs显式关闭思考模式以降低延迟与输出噪声。 - 解析并规范化:
parseAiResponse解析出 JSON 对象后取slug字段,再经normalizeSlug处理(默认小写,除非设置了NUXT_CASE_SENSITIVE,逻辑在 server/utils/link-store.ts),最终返回{ slug }。
降级行为
只要模型调用抛出异常,端点不会报错给前端,而是返回基于 URL 的简单建议:取 URL 路径的最后一段(取不到则取 hostname),把非[A-Z0-9-]字符替换为-、截断到 50 字符、去掉首尾连字符(空则回退为link)。测试returns a fallback slug when Workers AI fails验证了:当AI.run与AI.toMarkdown同时失败时,/api/link/ai?url=.../fallback-slug仍返回 200 且slug为fallback-slug(见 tests/api/link.spec.ts)。
端点二:/api/link/og-ai —— 社交预览标题与描述
接口定义在 server/api/link/og-ai.get.ts,用于生成 OpenGraph 卡片的title与description。
请求与参数
GET /api/link/og-ai?url=<目标链接>[&locale=<语言标签>]url:必填,zodz.url()校验,缺失/非法返回 400;locale:可选,指定生成文案的偏好语言。
locale的处理比较讲究:resolveMetadataLocale 会先用Intl.getCanonicalLocales把传入值规范化为合法语言标签;解析失败或未传时,回退到当前请求自身的重定向语言(resolveRedirectLocale(event)),而不是凭空猜测。规范化后的语言会被追加到系统提示词末尾:
${aiOgPrompt}\nGenerate the title and description in the language matching this locale: ${locale}.系统提示词(aiOgPrompt默认值)要求模型把页面总结为 OpenGraph 预览的标题与描述,并固定输出{"title": "...", "description": "..."}的 JSON 结构,对话中同样携带两组 few-shot 示例(Cloudflare 与 Nuxt 的官方简介文案)。
字段级降级
与 slug 端点的“整体回退”不同,og-ai 是按字段独立回退的:模型调用失败时走fallbackMetadata(url)(title 取去掉www.前缀的 hostname,description 为Short link for ${url};URL 解析失败则用通用文案);调用成功但某个字段缺失或为空时,也只回退该字段,例如:
const title = String(result.title ?? '').trim() || fallback.title const description = String(result.description ?? '').trim() || fallback.description测试用例验证了 AI 全挂时title为example.com、description非空(tests/api/link.spec.ts)。
页面抓取:fetchPageMarkdown 的边界与防护
两个端点共用 server/utils/markdown.ts 中的fetchPageMarkdown来获取目标页内容,它的实现体现了在 Serverless 环境下“读任意外部页面”的安全边界:
- 协议白名单:只处理
http:/https:,其他协议直接返回null(不抓取页面,只用裸 URL 提问); - 5 秒超时:
AbortController+setTimeout控制整个抓取; - 256 KB 字节上限:流式读取 body,到达上限即
reader.cancel(),从源码注释看目的是“bound memory and upstream transfer”,避免超大页面撑爆 Worker 内存; - 内容类型分流:若响应本身是
text/markdown直接使用;若是text/html,则交给 Workers AI 的AI.toMarkdown()转成 Markdown;转换失败仅告警并降级为“无页面内容”; - 4096 字符截断:进入提示词的页面正文最多 4096 字符(
MAX_MARKDOWN_LENGTH); - 语言透传:把当前请求的
Accept-Language头转发给目标站,优先取回对应语言的内容。
任何一步失败(非 2xx、超时、转换失败)都被捕获并告警,最终返回null——这正好衔接上文两个端点的“裸 URL 提问”路径,保证 AI 功能在目标页不可读时依然可用。
响应解析:parseAiResponse 的容错设计
Workers AI 不同模型的返回结构并不统一(有的直接给response字符串,有的套在choices[0].message.content里),且模型偶尔会用 Markdown 代码块包裹 JSON。server/utils/ai.ts 中的parseAiResponse统一处理了这些情况:
- 兼容
response与choices[0].message.content两种取值位置; stripCodeFence剥掉形如```json ... ```的外层代码围栏(仅当首行是```或```json时);- 用
destr安全解析 JSON,只有结果是“对象”(排除数组与null)时才返回,否则返回空对象。
返回空对象时,两个端点分别按前述策略回退(slug 走 URL 派生,og-ai 走 hostname 派生),保证接口永远返回可用的结果。
前端调用点:链接编辑器中的两处集成
AI 能力最终服务于仪表盘的链接编辑器:
- 基础表单中“AI 生成 slug”:app/components/dashboard/links/editor/Form.vue 调用
/api/link/ai?url=...,成功后把result.slug写入表单的slug字段; - 高级设置中的社交预览:app/components/dashboard/links/editor/Advanced.vue 调用
/api/link/og-ai,把返回的title、description填入链接的元数据字段。
两个调用点都只做“填充表单”,不自动保存——与文档中 “Always review before saving”(保存前请人工复核)的要求一致。
数据隐私注意事项
文档用 warning 级别强调:页面内容与目标 URL 可能会被发送到 Cloudflare Workers AI。具体来说,目标页正文(截断到 4096 字符)与完整 URL 会出现在发给模型的messages中,og-ai 还会附带 locale。如果你的实例可能处理敏感链接(内部系统、未公开页面、含隐私参数的链接),需要先评估数据敏感度与相关政策,再决定是否开启AI绑定;不开启对短链核心功能没有任何影响。
小结
Sink 的 Workers AI 集成可以概括为一条“能算则算、算不了就兜底”的链路:绑定AI→ 抓取页面(5s 超时 / 256KB / 4096 字符的三重边界)→ few-shot 提示词请求结构化 JSON → 容错解析 → 失败时按字段或整体回退到 URL 派生结果。三个环境变量(NUXT_AI_MODEL、NUXT_AI_PROMPT、NUXT_AI_OG_PROMPT)覆盖默认行为,其中自定义 slug 提示词必须保留{slugRegex}占位符以维持格式约束。相关实现与测试可分别在 server/api/link/ai.get.ts、server/api/link/og-ai.get.ts、server/utils/markdown.ts、server/utils/ai.ts 和 tests/api/link.spec.ts 中查证。
【免费下载链接】Sink⚡ A Simple / Speedy / Secure Link Shortener with Analytics, 100% run on Cloudflare.项目地址: https://gitcode.com/GitHub_Trending/si/Sink
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考