ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Supabase实战:为AI应用构建数字总部基础设施

Supabase实战:为AI应用构建数字总部基础设施 最近帮几个朋友看他们的 AI 项目发现一个特别有意思的共性大模型调用、Prompt 编排、Agent 工作流这些都能折腾得明明白白但一提到用户登录怎么做聊天记录存哪知识库的向量往哪里放大家就集体卡住了。说白了AI 应用和传统应用最大的区别在于AI 应用天然需要一个数字总部——用户身份、会话历史、向量记忆、实时推送、业务逻辑全都得有一个可靠的地方来承接。这个总部就是基础设施。这篇文章要聊的是我最近用 Supabase 做 AI 应用基础设施的完整过程。Supabase 是开源界的 Firebase 替代品但它底层直接跑 PostgreSQL这意味着除了常规的数据库、认证、文件存储、实时订阅之外你还能拿到 Postgres 生态里的 pgvector 做向量检索用 Edge Functions 跑 AI 业务逻辑。对想快速把 AI 想法落地成产品的人来说这一套东西足够撑起一个小型数字总部了。全文我会覆盖从 Docker 本地部署、表结构设计、Auth 与行级安全策略、Realtime 实时消息到 Edge Functions 调大模型的实际案例也会把我在项目中踩过的坑和沉淀下来的套路一并交代清楚。内容偏实战适合正在做 AI 应用、但后端基础设施还没想明白的朋友。1. 先想清楚AI 应用的基础设施到底缺什么1.1 一张基础设施需求清单动手写代码之前我习惯先把需求清单列出来。AI 应用跟普通 CRUD 应用不太一样它多出来的几个硬需求是很多人一开始没意识到的。第一是用户体系。哪怕你做的是纯工具类的 AI 应用只要涉及我的对话记录我的收藏我的知识库就必须有用户身份。自己写一套注册登录、Token 签发、密码重置工作量不大但很琐碎而且容易在安全细节上翻车。第二是会话数据持久化。AI 应用的核心资产就是用户和大模型之间的对话历史。没有持久化刷新一下页面对话就没了用户根本不会回来。这里涉及的不只是把 messages 存进数据库还要考虑会话列表怎么查、消息怎么分页、上下文怎么拼装。第三是向量存储。凡是做 RAG检索增强生成或者给 AI 加记忆的应用都需要把文本 embedding 之后存进向量数据库。市面上的专用向量库比如 Pinecone、Milvus很好但对大部分中小项目来说单独引入一套向量库意味着多一个要运维的组件成本并不低。第四是实时能力。现在的 AI 应用几乎默认要流式输出用户发一句话AI 一个字一个字往外吐。这个体验背后要么是 WebSocket 长连接要么是 SSE 推送如果整个消息链路还要经由自己的后端中转实时通道的设计就得提前想好。第五是业务逻辑的承载位置。AI 编排逻辑比如 Agent 决定调用哪个工具、多步推理怎么执行到底放在前端、独立后端还是云函数这直接决定了后续的可维护性和安全性。把这五条列完你会发现它们并不是彼此独立的模块而是互相咬合的一套体系。比如用户身份决定了会话数据的归属会话数据又关联到向量记忆的范围实时推送又依赖前面这些数据的变更事件。这正是为什么我倾向于用一个整合型平台来做而不是散装拼一堆服务。1.2 Supabase 与自己搭一套后端的取舍面对上面那张清单常规思路是后端用 Node.js 或 Spring Boot 自己搭数据库用 MySQL 或 Postgres然后接 Redis、接对象存储、接 WebSocket。这套方案不是不行但对于一个人或一个小团队来说光是把这些组件都部署起来、做好联调就得花掉两周时间而且后续每一层都要自己维护。Supabase 的思路是把这些打包成一套开发平台。它底层是 PostgreSQL然后在这个基础之上长出了几块能力能力对应传统方案说明Auth自建登录注册/JWT支持邮箱密码、OAuth、Magic Link自带用户表DatabasePostgreSQL 迁移工具原生 SQL支持外键、视图、函数、触发器Storage对象存储S3/MinIO图片、文件、私有/公有桶RealtimeWebSocket 服务订阅数据库变更、支持 Broadcast 与 PresenceEdge FunctionsServerless 函数基于 Deno可放密钥、调第三方 APIVector单独向量库PostgreSQL 扩展 pgvectorSQL 里直接做相似度检索这个组合对 AI 应用来说特别顺手因为你在一个地方就能把用户登录 对话落库 向量检索 消息推送 业务编排整体打通所有模块共用同一套 Postgres 数据模型不用在多个系统之间同步数据。当然它也有取舍。如果你需要非常复杂的数据库查询优化、自定义的网关策略、或者每秒上万 QPS 的高性能场景那你依然需要更专门的架构。但对绝大多数 AI 创业项目、内部工具、个人作品来说Supabase 能帮你把 80% 的基础设施成本直接砍掉。1.3 数字总部的边界什么归 Supabase什么归大模型我见过不少项目把 Supabase 当成什么都能干的瑞士军刀也有人反过来不敢把业务逻辑放进去。这两种倾向都不太对。我自己的划分原则很朴素凡是和用户、数据、状态相关的归 Supabase凡是和智能决策、内容生成相关的归大模型。举个具体的例子。一个 AI 客服助手用户消息先通过 Edge Function 转发给大模型大模型决定是直接回答还是调用工具查订单。这个决定的过程是智能部分归大模型但订单数据本身要从 Supabase 查出来查出来的结果再回传给大模型生成最终回答这个数据存取归 Supabase。如果大模型需要记住用户偏好那 embedding 向量存在 Supabase 的 pgvector 里每次对话前先用向量检索找回相关记忆再拼进 Prompt。把边界划清楚之后整个系统就很好理解了Supabase 是数字总部负责场地和档案大模型是外聘专家负责给出判断和内容。你的 AI 应用代码就是连接这两者的调度员。2. 本地起一套 SupabaseDocker Compose 部署实战2.1 环境准备与版本选择我强烈建议在正式开发之前先把 Supabase 跑在本地。原因很简单云端的免费项目有地域和网络延迟问题而且在开发调表结构、反复清数据的时候本地环境能让你放开手脚折腾。本地部署最省事的方式是官方 CLI。前提是你装了 Docker新版 CLI 已经内置了对容器运行时的管理甚至 Docker Desktop 都不太需要然后一条命令就能把整个服务栈拉起来。# 安装 Supabase CLImacOSLinux 类似 brew install supabase/tap/supabase # 初始化项目会在当前目录生成 supabase/ 配置目录 supabase init # 启动本地服务 supabase startsupabase start做的事比你想象的多它会拉取一套 Docker 镜像里面包含 Postgres、KongAPI 网关、Auth、Storage、Realtime、Edge Functions 运行时、以及一个本地管理面板。第一次启动因为要拉镜像通常需要几分钟之后启动就快了。启动完成后CLI 会输出一串本地地址。默认情况下API 地址http://localhost:54321本地管理面板Studiohttp://localhost:54323数据库连接串postgresql://postgres:postgreslocalhost:54322/postgres需要特别提醒的是新版本的 CLI 默认带的镜像和云端版本严格对应所以本地开发的表结构、RLS 策略、Edge Functions推到云端时不会出现本地能跑云端挂了的版本落差。2.2 配置文件内的关键参数项目初始化之后supabase/config.toml是核心配置文件。你不需要改里面所有东西但有几个参数建议提前了解省得后面找半天。[api] enabled true port 54321 schemas [public, graphql_public] [auth] enabled true # JWT 过期时间开发环境可以调长一点 jwt_expiry 3600 enable_signup true [edge_runtime] enabled true port 54324 [db] port 54322 # 本地数据库根密码云端不可用 major_version 15这里值得多说一句schemas。默认情况下 API 暴露的是publicschema如果你后面把业务表放在private或者authschema 里前端通过 API 查不到需要在schemas里显式加进去。这是个很隐蔽的坑我见过有人排查了半天为什么接口返 404结果只是 schema 没配。改完配置之后重启本地服务生效supabase stop supabase start2.3 启动验证与常见启动失败启动成功不是看终端那几行日志就行了我一般会做三个验证打开 Studio 面板http://localhost:54323确认 Table Editor 里能看到内置的 auth.users 表。在终端请求一下健康接口curl http://localhost:54321/rest/v1/如果能返回 API 根信息说明网关正常。用supabase status命令查看所有服务端口是否都在监听。我第一次启动就翻过一次车Docker 网段冲突导致 Realtime 服务起不来。CLI 报错信息很笼统只提示container failed to start。排查方式是逐一看每个容器状态发现是 host 上有服务占用了 54321 端口。换个端口或者在 Docker Desktop 里重置网络就好了。如果你也遇到容器起不来的情况记住一个原则先看端口占用再看磁盘空间最后看镜像版本是否完整这三样排查完90% 的问题都能定位。2.4 自托管与云端的选型建议很多人会纠结一个问题本地跑通了之后正式环境用 Supabase 官方云服务还是自己租服务器自托管我的建议是分阶段。项目早期、用户量还不大的时候直接上官方云免费版或者付费版省心且安全补丁有人管。等到用户量起来、有了明确的合规需求比如数据必须存国内、必须私有化再考虑自托管。自托管确实可行官方文档提供了完整的 Docker Compose 文件基本上把云端的核心组件都搬下来了。但自托管也意味着你要自己处理升级、备份、监控、安全加固这些工作远比想象中多。有一次我给一个朋友自托管了一套光是把备份脚本调通、验证恢复流程就花了一整天。所以我的态度是没有专门运维能力之前别轻易自托管把精力留给 AI 应用本身的业务逻辑才是划算的。3. 建表不只是建表数字总部的数据模型设计3.1 最小可用模型profiles、conversations、messagesAI 应用的表结构我从一个最小的AI 对话类应用模型说起。这个模型能覆盖大部分场景你在它上面加字段、加表就行。第一张表是用户资料表profiles。Supabase 的 Auth 模块会自带auth.users表但按官方推荐的做法业务相关的用户信息不直接往 auth.users 里塞而是建一张业务表用触发器在用户注册时自动插入一行create table public.profiles ( id uuid references auth.users on delete cascade primary key, display_name text, avatar_url text, created_at timestamptz default now() ); -- 注册时自动创建 profile create or replace function public.handle_new_user() returns trigger language plpgsql security definer set search_path public as $$ begin insert into public.profiles (id, display_name) values (new.id, coalesce(new.raw_user_meta_data-display_name, 新用户)); return new; end; $$; create trigger on_auth_user_created after insert on auth.users for each row execute procedure public.handle_new_user();第二张表是会话表conversations。它存的是一次对话的元信息比如标题、归属用户、最后活跃时间create table public.conversations ( id uuid default gen_random_uuid() primary key, user_id uuid not null references auth.users on delete cascade, title text default 新对话, created_at timestamptz default now(), updated_at timestamptz default now() ); create index conversations_user_id_idx on public.conversations (user_id);第三张表是消息表messages。它存的是每一轮问答注意要把用户消息和 AI 回复放在同一张表用role区分。这样查整个上下文时只需要一次查询、按时间排序create table public.messages ( id uuid default gen_random_uuid() primary key, conversation_id uuid not null references public.conversations on delete cascade, role text not null check (role in (user, assistant, system)), content text not null, created_at timestamptz default now() ); create index messages_conversation_id_created_at_idx on public.messages (conversation_id, created_at);这个三表模型几乎是 AI 对话类应用的通用骨架。你可以在此基础上扩展比如加models字段记录每次用了哪个大模型加tokens字段记录消耗加feedback字段做用户对回复的评价。但核心就这三个实体想清楚它们之间的关系后面的开发会顺很多。3.2 向量记忆pgvector 接入与 embedding 存储AI 应用的记忆功能我建议直接用 pgvector。这不是因为它最强大而是因为它和业务数据在同一个数据库里做关联查询时不需要跨系统搬运数据。首先启用扩展create extension if not exists vector;然后建一张记忆表或者直接在知识库表里加向量字段。以用户偏好记忆为例create table public.user_memories ( id uuid default gen_random_uuid() primary key, user_id uuid not null references auth.users on delete cascade, content text not null, -- 记忆原文 embedding vector(1536), -- OpenAI 的 text-embedding-3-small 是 1536 维 created_at timestamptz default now() ); create index user_memories_embedding_idx on public.user_memories using ivfflat (embedding vector_cosine_ops);这里有个选择要提醒你ivfflat是近似索引建索引快、占空间小但检索精度稍低如果数据量不大几万条以内甚至可以不建向量索引直接全表扫描做余弦相似度排序速度完全够用。另一个是hnsw索引精度高、查询快但建索引慢、占内存。我个人的经验是数据量在 5 万条以下用hnsw超过 10 万条再评估是否需要独立的向量库。写入记忆和检索记忆的逻辑也不复杂。写入时先调 embedding 接口拿到向量再插进表里检索时把当前对话内容转成向量按余弦距离排序取 Top Kselect content, 1 - (embedding $query_embedding) as similarity from public.user_memories where user_id $current_user order by embedding $query_embedding limit 5;是 cosine 距离运算符值越小越相似所以排序用order by embedding ...而不是反过来。这是我第一次写就搞反了的地方特地说一声免得你重复踩。3.3 用迁移脚本管理 schema直接在 Studio 的 SQL Editor 里建表当然方便但一旦上了生产环境你就需要可控的 schema 变更记录。Supabase CLI 提供了一套完整的迁移管理流程# 创建迁移文件 supabase migration new init_schema # 编辑 supabase/migrations/xxx_init_schema.sql 之后执行迁移 supabase db push这套机制和常见的数据库迁移工具一样本地执行过的迁移会被记录下来推到云端时会按顺序执行。好处是整个团队的表结构变更都能走版本管理代码评审的时候顺便就把 SQL 审了。我的习惯是任何表结构改动都走迁移脚本不在 Studio 里手动改生产库。哪怕只是加一个索引也要走迁移。4. 安全关口Supabase Auth 与 RLS 策略4.1 Auth 模块的定位与接入方式Supabase Auth 不是一个简单的登录功能它提供了一整套身份体系用户注册、邮箱验证、密码重置、第三方 OAuth、JWT 签发与刷新。你从前端接入时官方 SDK 会帮你管理 access token 和 refresh token调用 API 时自动带上 Authorization 头。前端接入的最小代码长这样import { createClient } from supabase/supabase-js const supabase createClient( https://your-project.supabase.co, YOUR_ANON_KEY ) // 注册 await supabase.auth.signUp({ email: userexample.com, password: password123 }) // 登录 const { data, error } await supabase.auth.signInWithPassword({ email: userexample.com, password: password123 })拿到用户之后通过supabase.auth.getUser()获取当前用户 ID所有业务查询都会带上这个用户维度。4.2 行级安全策略的写法与陷阱行级安全Row Level SecurityRLS是 Supabase 最核心的安全机制也是新手最容易忽略的一环。RLS 的本质是即使你拿到了数据库的访问密钥也只有在 SQL 层面通过了表上的策略才能读写对应行。默认情况下新表的 RLS 是关闭的这意味着任何持有 anon key 的人都能读这张表。上线前必须给每张业务表开启 RLSalter table public.profiles enable row level security; alter table public.conversations enable row level security; alter table public.messages enable row level security; alter table public.user_memories enable row level security;然后创建策略。这里我以一个会有多用户访问的应用为例策略的通用模板是用户只能操作自己的数据create policy profiles_select_own on public.profiles for select using (auth.uid() id); create policy conversations_select_own on public.conversations for select using (auth.uid() user_id); create policy messages_select_own on public.messages for select using ( exists ( select 1 from public.conversations c where c.id messages.conversation_id and c.user_id auth.uid() ) );注意 messages 的策略不能直接写auth.uid() user_id因为 messages 表里没有 user_id 字段只能通过关联 conversations 来判断当前用户是否有权看这条消息。这是表设计时就要考虑清楚的没有冗余用户ID的多层关联表RLS 策略会写得相对复杂。RLS 还有一个容易踩的坑security definer函数会绕过 RLS。比如 3.1 节里那个自动创建 profile 的触发器它用了security definer执行时是以函数所有者身份运行的所以可以往 profiles 表插入数据。但如果你在函数里不小心暴露了不该暴露的数据RLS 就帮不上忙了。我的建议是函数默认用security invoker以调用者身份运行实在需要提权再显式写成security definer并且里面所有表都要开启 RLS。4.3 API Keys 与密钥管理Supabase 有两类 API Keyanon key和service_role key。anon key 是给前端用的配合 RLS 把权限限制在用户自己的数据范围内service_role key 拥有绕过 RLS 的超级权限只能在后端或 Edge Functions 里用绝对不能出现在前端代码里。我见过不少人图省事在前端直接用 service_role key 或者关闭 RLS结果数据库被人脱裤。这里有个必须养成的强迫症任何存储在环境变量里的 key都要分环境隔离前端用的只能是 anon key任何需要提权的操作都放到 Edge Functions 里做用 service_role key 在服务端调用。如果你用的是官方云端托管控制台的 API 设置面板里能看到这两个 key如果是本地 Docker 部署CLI 启动时输出的那串就是对应环境的 key。5. 实时能力让 AI 的回复逐字出现5.1 Realtime 的原理与选型流式输出已经成为 AI 应用体验的及格线了。实现方式无非三种前端直接连大模型的 SSE 流、后端中转后转发、通过 Supabase Realtime 广播。如果你的大模型 API 允许前端直连并且不担心密钥暴露那最简单的是前端直接接 SSE。但在生产环境里大模型的 API key 不应该出现在浏览器里所以更稳妥的方案是Edge Function 接收用户请求调用大模型的流式接口再把内容片段通过 Realtime 的 Broadcast 能力推给客户端。Supabase Realtime 提供了两种通道一个是数据库变更订阅Postgres Changes另一个是 Broadcast 和 Presence。AI 流式输出这类高频、临时、不需要持久化的消息用 Broadcast 最合适聊天记录这种需要落库的数据变更用 Postgres Changes 订阅。5.2 从数据库变更到前端订阅的完整链路以AI 回复写入 messages 表后前端实时收到这个场景为例。假设 Edge Function 已经把 AI 回复完整写入了 messages 表前端要做的是订阅这张表的 insert 事件const channel supabase .channel(messages-watch) .on( postgres_changes, { event: INSERT, schema: public, table: messages }, (payload) { console.log(新消息, payload.new) // 追加到对话界面的消息列表 } ) .subscribe()这里有个和我前面说的 RLS 呼应的点Realtime 订阅同样受 RLS 约束。也就是说如果 messages 表上的 select 策略不允许用户读到某条记录那么这条记录即使发生变更订阅者也不会收到。所以不要以为只要开了 Realtime 就能看到所有数据前面把 RLS 写好这里就自动安全了。如果你的场景需要逐字输出而不是整条消息到达后再显示那你需要的是 Broadcast。Edge Function 拿到大模型流式响应的每个 chunk 之后先推给前端展示全部完成后再把完整内容写入数据库。这样既保证了体验流畅又不丢失最终的持久化记录。// Edge Function 里推送消息片段 const channel supabase.channel(ai-stream) await channel.subscribe() await channel.send({ type: broadcast, event: token, payload: { conversation_id, content: chunk } })// 前端订阅 const channel supabase.channel(ai-stream) channel.on(broadcast, { event: token }, (payload) { // 累加内容更新 UI }) channel.subscribe()这个方案有一个细节容易忽略Realtime 的连接状态。如果用户网络抖动导致订阅断开前端要主动重连否则会漏掉中间的流式片段。我在实际项目里加了一个简单的重连机制onStatusChange里检测SUBSCRIBED之外的异常状态延迟 3 秒后重新 subscribe。6. Edge Functions把 AI 编排逻辑搬进数字总部6.1 Edge Functions 适合做什么Edge Functions 是 Supabase 里的 Serverless 函数基于 Deno 运行时和数据库、Auth、Storage 天然集成。它不是万能的服务端但有几个非常适合的用途存放服务端密钥代理大模型 API 调用做需要提权的数据库操作用 service_role key实现 AI Agent 的部分编排逻辑比如工具调用、多步推理处理 Webhook 回调比如支付通知、第三方平台事件我建议的原则是轻量编排放 Edge Function重量业务逻辑放独立后端。如果逻辑超过 200 行或者依赖很多 npm 包我会倾向于拆成独立服务而不是硬塞进 Edge Function 里。6.2 一个调用大模型的函数示例下面是一个最精简的 Edge Function作用是接收前端请求调用大模型接口然后把结果写回 messages 表。重点看它怎么用 service_role key 提权、怎么读环境变量import { createClient } from jsr:supabase/supabase-js2 const supabase createClient( Deno.env.get(SUPABASE_URL)!, Deno.env.get(SUPABASE_SERVICE_ROLE_KEY)! ) Deno.serve(async (req) { const { conversationId, content } await req.json() // 1. 把用户消息落库 await supabase.from(messages).insert({ conversation_id: conversationId, role: user, content }) // 2. 取最近的对话上下文 const { data: history } await supabase .from(messages) .select(role, content) .eq(conversation_id, conversationId) .order(created_at, { ascending: true }) .limit(10) // 3. 调用大模型 const messages history.map(m ({ role: m.role, content: m.content })) const response await fetch(https://api.openai.com/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${Deno.env.get(OPENAI_API_KEY)} }, body: JSON.stringify({ model: gpt-4o-mini, messages, stream: false }) }) const data await response.json() const reply data.choices[0].message.content // 4. 把 AI 回复落库 await supabase.from(messages).insert({ conversation_id: conversationId, role: assistant, content: reply }) return new Response(JSON.stringify({ reply }), { headers: { Content-Type: application/json } }) })部署这个函数很简单supabase functions deploy call-ai然后通过https://project-ref.supabase.co/functions/v1/call-ai调用。6.3 密钥管理与安全边界Edge Functions 里的环境变量是一个很容易被忽略的安全点。部署时用supabase secrets set设置而不是把密钥硬编码在代码里supabase secrets set OPENAI_API_KEYsk-xxxx还有一个重要细节Edge Function 默认也是可以通过 anon key 被任何人调用的。所以函数内部的鉴权逻辑不能省。最简单的做法是从请求头里解析 JWTconst authHeader req.headers.get(Authorization) const token authHeader?.replace(Bearer , ) const { data: { user }, error } await supabase.auth.getUser(token) if (error || !user) { return new Response(Unauthorized, { status: 401 }) }拿到 user 之后函数里再做数据操作时就能按 user.id 来限定写入范围避免 A 用户篡改 B 用户的会话。7. 实战踩坑我在这个过程中踩过的五类问题7.1 本地镜像与云端版本的隐性差异我有一阵子用本地环境开发一切正常结果把迁移推到云端之后发现某个 RLS 策略在云端一直报语法错误。排查到最后发现是本地的 Postgres 大版本是 15云端项目是 14某个 SQL 特性在 14 上不支持。这个教训告诉我创建云端项目时尽量选择和自己本地一致的数据库大版本本地升级 CLI 之后最好先跑一遍迁移确认没问题再推生产。7.2 RLS 误伤能登录看不到数据最典型的现象是Supabase Auth 登录成功了但前端的列表查询返回空数组。这时候先不要怀疑代码逻辑九成是 RLS 策略没写对。我当时写 sessions 表的 select 策略时忘了把 JWT 的auth.uid()和会话的user_id对上导致所有查询都被策略拦下。排查方法很简单在 Studio 的 SQL Editor 里用select auth.uid();模拟当前用户然后手动执行业务查询看是否返回数据。如果手动查询也被拦那就是策略写错了如果手动能查出来但前端查不到那就是前端没带 Authorization 头。7.3 Realtime 连接被 RLS 静默拦截Realtime 订阅失败的报错往往很模糊甚至不报错就是收不到消息。我第一次做流式更新时前端订阅一直没反应调试了很久才发现是 messages 表的 RLS select 策略没有覆盖到 Realtime 订阅用的服务身份。解决方法其实还是要回到Realtime 遵循 RLS这个本质上确保订阅者也就是登录用户对目标行有 select 权限。这里有个小技巧在 Realtime 订阅里只监听用户自己有权读的行尽量用filter参数精确定位而不是全表监听。全表监听的性能开销大而且会把不该暴露的变更事件推到前端。7.4 Edge Function 冷启动延迟Edge Functions 虽然叫 Edge但冷启动延迟是真实存在的尤其在国内网络环境下首次请求可能需要两三秒。如果你的 AI 应用对首字延迟特别敏感建议做两件事一是给函数做一个预热机制定时发一个轻量请求保持实例存活二是把大模型调用设计成异步任务用 Realtime 推送结果而不是让前端傻等函数执行完毕。7.5 连接池耗尽与连接泄漏我早期在 Edge Function 里每次请求都createClient并且没有主动释放数据库连接。在高并发场景下Postgres 的连接数被打满服务直接不可用。后来改成全局复用客户端实例并且在大量查询的场景下使用 Postgres 连接池参数问题才缓解。// 在模块顶层创建一次而不是在 Deno.serve 里重复创建 const supabase createClient( Deno.env.get(SUPABASE_URL)!, Deno.env.get(SUPABASE_SERVICE_ROLE_KEY)!, { auth: { persistSession: false } } )这个细节看着小但在 Serverless 场景下是命门级别的优化。8. 我现在的项目里数字总部是怎么运转的最后分享一下我当前一个 AI 知识助手项目里的实际运转流程算是一个整体串讲。用户从前端登录身份走 Supabase Auth。登录后创建会话conversations 和 messages 两张表记录每一轮问答。用户上传文档时文件进 Storage后台任务把文档切块、生成 embedding、写入带有 pgvector 的 documents 表。用户提问时Edge Function 先根据问题向量从 documents 表检索 Top 5 相关片段拼进 Prompt 后调用大模型再把完整回答写入 messages 表。前端通过 Realtime 订阅在 AI 回答写完的瞬间收到更新不需要轮询。整条链路里大模型只是被调用的一方所有状态和数据都在 Supabase 这个数字总部里流转。这套架构的好处是我不用单独维护后端服务、不用部署向量库、不用考虑 WebSocket 集群一个人就能把完整的产品闭环跑起来。如果某天业务量真的上来了Supabase 这一层可以平滑迁移到独立服务大模型那层本来就是无状态的随时可以替换。从这个角度看选择 Supabase 作为 AI 应用的数字总部不只是省事更是给未来的演进留了余地。如果你正在做一个 AI 应用并且卡在基础设施怎么搭这个问题上我的建议是先别急着写业务代码照着这篇文章把本地 Supabase 跑起来把 Auth、数据库、RLS、Realtime、Edge Functions 这五块逐一过一遍。等这一套都通了你会发现 AI 应用的地基比想象中要稳得多。
返回列表