首页
/ Supabase 实战:用 Edge Functions + Huggingface.js + Storage 自动生成图片描述(Database Webhooks 驱动)

Supabase 实战:用 Edge Functions + Huggingface.js + Storage 自动生成图片描述(Database Webhooks 驱动)

2026-09-06 18:17:50作者:蔡怀权

导读

本文围绕仓库中 huggingface-image-captioning 示例函数 展开,讲解如何把 Supabase Edge Functions、Supabase Storage 和 Database Webhooks 组合起来,搭建一个“图片一上传即自动生成文字描述并落库”的自动化服务。读完本文,你将掌握 Webhook 载荷解析、Storage 签名 URL 生成、Huggingface.js 推理调用,以及基于 @supabase/server 的服务端管理员客户端写法,可直接在自有项目中复刻这套事件驱动 AI 工作流。

整体架构:四块积木拼出一个自动流水线

该示例的核心思路是“事件触发、函数中转、AI 推理、结果落库”,由四个 Supabase/Hugging Face 能力拼接而成:

  • Supabase Edge Functions:基于 Deno 运行、用 TypeScript 编写、通过 Supabase CLI 部署的无服务器函数,负责接收 Webhook 并编排整个流程;
  • Huggingface.js:Hugging Face 官方 JS 库,用统一接口调用 10 万+ 机器学习模型。本示例通过 HfInference 调用图像转文本(image-to-text)模型完成描述生成;
  • Supabase Storage:存放用户上传图片的存储桶;函数通过它生成带时效的签名 URL 供模型拉取图片;
  • Database Webhooks:数据库变更触发器。当 storage.objects 表插入新记录(即有文件上传成功)时,自动向 Edge Function 发送 HTTP 请求,无需任何额外编排代码。

从事件流来看,整条链路是:

  1. 用户把图片上传到名为 images 的 Storage bucket,Postgres 的 storage.objects 表插入一行;
  2. 预先配置的 Database Webhook 捕获到 INSERT 事件,把包含该文件元数据的 JSON 载荷 POST 到已部署的 huggingface-image-captioning 函数;
  3. 函数解析载荷,用管理员权限为刚上传的对象生成 60 秒有效的签名 URL,并抓取图片二进制数据;
  4. 函数调用 Hugging Face 上的视觉描述模型,得到一句话文字描述;
  5. 函数把 storage.objects 的主键和生成的描述写入 image_caption 表,整个流程对调用方完全异步。

核心实现逐行拆解:index.ts

该示例的全部逻辑浓缩在 index.ts 一个文件里,完整约 46 行,下面分段展开。

依赖导入与推理客户端初始化

import { HfInference } from 'npm:@huggingface/inference@^4'
import { withSupabase } from 'npm:@supabase/server@^1'

import { Database } from './types.ts'
  • npm:@huggingface/inference@^4:Huggingface.js 推理库的 Deno 兼容导入写法,直接使用 npm 远程模块,无需安装依赖;
  • npm:@supabase/server@^1:Supabase 官方服务端工具包,withSupabase 包装器会在请求处理前自动解析 JWT、初始化带权限的 Supabase 客户端,并把 ctx 注入到 handler;
  • Database 类型来自同目录的 types.ts,提供 storagepublic 两个 schema 的完整表结构类型,让整条链路的字段访问都有类型保障。

随后是推理客户端与函数入口的日志输出:

console.log('Hello from `huggingface-image-captioning` function!')

const hf = new HfInference(Deno.env.get('HUGGINGFACE_ACCESS_TOKEN'))

HUGGINGFACE_ACCESS_TOKEN 从环境变量读取,需要在函数部署后用 supabase secrets set 配置。Hugging Face 的推理接口(Inference API)通常要求提供 Access Token,你可以在 Hugging Face 账户设置中创建 token 并把它绑定为 Supabase 项目 secret。

Webhook 载荷类型定义

type SoRecord = Database['storage']['Tables']['objects']['Row']
interface WebhookPayload {
  type: 'INSERT' | 'UPDATE' | 'DELETE'
  table: string
  record: SoRecord
  schema: 'public'
  old_record: null | SoRecord
}

SoRecord 直接取自生成的类型文件 types.tsstorage.objects 表的 Row 类型,其字段包括 bucket_ididnamemetadatapath_tokensowner 等。从该结构可以看出,storage.objects 就是 Storage 的元数据表——每次文件上传都会在其中产生一条记录。WebhookPayload 则描述了 Supabase Database Webhooks 发送的 JSON 结构,包含事件类型、触发表、变更后的新记录 record 与变更前记录 old_record

请求处理:签名 URL → 拉图 → 推理 → 落库

// Deploy with verify_jwt = false.
export default {
  fetch: withSupabase<Database>({ auth: 'secret' }, async (req, ctx) => {
    const payload: WebhookPayload = await req.json()
    const soRecord = payload.record

代码注释与 config.toml[functions.huggingface-image-captioning] verify_jwt = false 的设置互相印证:由于该函数是被数据库 Webhook 以服务端到服务端方式调用的,而非来自浏览器客户端,因此关闭了 JWT 校验,这也是 Webhook 类 Edge Function 的通用部署要求。

withSupabase<Database>({ auth: 'secret' }, handler) 是核心。仓库中另一个示例 og-image-with-storage-cdn 也采用相同的 withSupabase({ auth: 'secret' }, ...) 调用模式,说明这是服务端函数获得管理员权限的标准写法。传入 auth: 'secret' 后,handler 的 ctx.supabaseAdmin 即绑定 service role(服务角色密钥)的管理员客户端,可绕过 RLS 直接读写数据表,这正是本函数能够在无用户上下文的情况下向 image_caption 表写入记录的前提。

    // Construct image url from storage
    const { data, error } = await ctx.supabaseAdmin.storage
      .from(soRecord.bucket_id!)
      .select(...)

接着逻辑分四步展开(结合代码结构与 Storage API 语义):

  1. 生成签名 URL:以 soRecord.bucket_id 选中存储桶,用 path_tokens 拼接出对象路径,调用 createSignedUrl(path, 60) 生成 60 秒有效的临时访问地址。path_tokens 是路径分段数组(如 ["folder", "photo.jpg"]),用 join('/') 还原为完整对象键;
  2. 拉取图片二进制await fetch(signedUrl) 得到响应后取 .blob(),作为推理接口的输入数据;
  3. 调用视觉模型
    const imgDesc = await hf.imageToText({
      data: await (await fetch(signedUrl)).blob(),
      model: 'nlpconnect/vit-gpt2-image-captioning',
    })
    
    hf.imageToText 是 Huggingface.js 的图像转文本方法,model 指向公开模型 nlpconnect/vit-gpt2-image-captioning——这是一个基于 Vision Transformer + GPT-2 的经典图像描述生成模型。返回的 imgDesc.generated_text 即模型生成的图片描述文本。如需更换模型,只需替换该 model 标识;
  4. 写回数据库
    await ctx.supabaseAdmin
      .from('image_caption')
      .insert({ id: soRecord.id!, caption: imgDesc.generated_text })
      .throwOnError()
    
    storage.objects 的记录主键 soRecord.id 作为 image_caption.id,把描述写入 caption 列。.throwOnError() 会在写入失败时抛出异常,让函数返回非 2xx 状态以便 Webhook 重试。最后 return Response.json({ status: 'ok' }) 通知 Webhook 处理成功。

前置数据准备:bucket 与 image_caption 表

README 的 Setup 步骤,需要先在数据库中准备两个对象:

  1. 创建 Storage bucket images:在项目 Dashboard 的 Storage → Buckets 中新建名为 images 的存储桶。参考仓库 config.toml 中本地开发配置,示例还把该 bucket 设成了 public = true 并绑定 objects_path,用于把本地 ./buckets/images 目录同步为存储内容;
  2. 建表 image_caption,仅两列:
    • iduuid 类型,外键引用 storage.objects.id,把每一条描述与具体的存储对象一一关联;
    • captiontext 类型,存放模型生成的描述文本。

建表时建议同时为 storage.objects 的插入事件配置触发器映射逻辑(即在 Webhook 表选择中按需勾选相关事件类型)。注意在仓库 migrations 目录中并没有为该示例提供建表迁移脚本,说明表结构需要按上述步骤在 Dashboard 或你自己的迁移中创建,字段定义与 types.tsimage_caption 表的 Row/Insert 类型保持一致(id: stringcaption: string)。

生成 TypeScript 类型

为保证 withSupabase<Database> 的泛型与 ctx.supabaseAdmin.from('image_caption') 的调用链类型完备,需要基于远端数据库生成类型文件:

supabase gen types typescript --project-id=your-project-ref --schema=storage,public > supabase/functions/huggingface-image-captioning/types.ts

参数说明:

  • --project-id:替换为你的项目 ref(Dashboard 项目设置中可查);
  • --schema=storage,public:关键点。本函数同时访问 Storage 的 objects 表与业务侧的 image_caption 表,因此必须同时导出 storagepublic 两个 schema,缺一不可。

生成结果即仓库中的 types.tspublic schema 下只有 image_caption 一张表,而 storage schema 下包含 bucketsmigrationsobjects 三张表及多个辅助函数(如 searchcan_insert_objectfoldername 等),完整描述了 Storage 内部的数据结构。

部署函数并配置 Webhook

部署 Edge Function

在项目根目录执行:

supabase functions deploy huggingface-image-captioning

由于函数走 Webhook 触发,务必确认 config.toml 中对该函数设置了:

[functions.huggingface-image-captioning]
verify_jwt = false

随后配置密钥环境变量:

supabase secrets set HUGGINGFACE_ACCESS_TOKEN=your_hf_token

创建 Database Webhook

在 Dashboard 的 Database → Webhooks(即 README 中提到的 Database Hooks)页面新建 Webhook,核心配置要点:

  • Source Table 选择 storage.objects,事件类型选择 INSERT(对应“图片上传完成”这一时机,与函数内 payload.type === 'INSERT' 的语义匹配);
  • HTTP 方法选 POST,URL 填已部署函数的完整调用地址;
  • HTTP Headers 中配置 Authorization: Bearer <anon key>(本地开发时可用 supabase functions serve --no-verify-jwt 绕过签名);
  • 事件载荷会自动携带表名、schema、新旧记录等字段,即 index.tsWebhookPayload 所声明的结构。

此后每当有新图片上传到 images bucket,Webhook 就会把 storage.objects 的新记录推送给函数,函数依次完成签名 URL 生成、模型推理与结果回写,全程无需客户端轮询或手动触发。

本地开发调试建议

结合 examples/edge-functions/README.md 提供的通用流程,本地调试该函数可参考以下步骤:

supabase start                                    # 启动本地栈(需 Docker)
cp supabase/.env.local.example supabase/.env.local
# 在 .env.local 中填入 HUGGINGFACE_ACCESS_TOKEN 等变量
supabase functions serve --env-file supabase/.env.local --no-verify-jwt

--no-verify-jwt 与 config.toml 中的 verify_jwt = false 相呼应,都是为了方便事件源(Webhook 或本地 curl)不经鉴权直接调用函数。调试时可以先向函数手动 POST 一条模拟的 storage.objects 记录,观察控制台日志中的 Hello from ... 与推理结果,再回到 Dashboard 验证 image_caption 表是否出现了对应行。需要注意,本地调试时 Storage 的对象必须真实存在于本地栈中,签名 URL 才能正确拉到图片数据。

边界与注意点(依据源码推断)

从源码结构与官方设计模式可以总结出以下工程注意点,供扩展该示例时参考:

  • 幂等性:示例直接以 storage.objects.id 作为 image_caption.id 主键。若同一对象被重复触发(如 Webhook 重试),INSERT 会因主键冲突失败。生产环境建议改为 upsert 或先查后写,保证事件重放安全;
  • 模型时延与超时imageToText 的耗时取决于所选模型与图片大小,Edge Functions 对执行时长有限制,超大图片应考虑先用 Storage 的图片处理能力压缩后再送推理;
  • Token 管理:推理消耗 Hugging Face 账户配额,HUGGINGFACE_ACCESS_TOKEN 属于机密,只能通过 supabase secrets set 注入,切勿写死在代码或提交到仓库;
  • 权限模型:全程使用 ctx.supabaseAdmin(service role),跳过 RLS,意味着该函数只应通过 verify_jwt = false 的 Webhook 入口暴露,避免被外部直接滥用。

小结

通过这个 示例 可以看到,事件驱动的 AI 能力接入并不复杂:storage.objectsINSERT Webhook 充当触发器,一个约 46 行的 Deno 函数借助 @supabase/server 的管理员客户端与 Huggingface.js 完成“取图 → 推理 → 落库”闭环,而模型本身可以随时通过 model 参数换成任意具备 image-to-text 能力的 Hugging Face 模型。这一模式同样可推广到语音转写、OCR、审核等其它“存储触发 + 模型推理”场景,值得作为你构建 Supabase AI 应用的基础模板。

登录后查看全文
热门项目推荐
相关项目推荐