Supabase + ModelRiver

具有 Supabase 的全栈人工智能应用。通过 pgvector 存储嵌入向量,用语义搜索查询相关数据,并通过 ModelRiver 回应。

概览

Supabase 是一款以开源身份对标 Firebase 的强大体系,并通过 pgvector 库原生构建了向量支持。与 ModelRiver 协同工作后,不仅能够使人工智能生成的模型向量随其他常见数据库应用数据混编一同存放,并且可以借用该体系内的高性能语义搜索引擎执行基于这些信息的发文应答。一切都在这一片体系平台内。


快速上手

安装相关依赖

Bash
pip install supabase openai

在 Supabase 中启用开启 pgvector

在 Supabase SQL 网页主控编辑器下挂上跑一遍这里的内容:

SQL
1-- pgvector
2create extension if not exists vector;
3 
4--
5create table documents (
6 id bigserial primary key,
7 content text not null,
8 metadata jsonb default '{}',
9 embedding vector(1536), --
10 created_at timestamptz default now()
11);
12 
13--
14create index on documents using ivfflat (embedding vector_cosine_ops) with (lists = 100);

配置启动相关客户端口

PYTHON
1from supabase import create_client
2from openai import OpenAI
3 
4# 导入 Supabase 大底
5supabase = create_client(
6 "https://YOUR_PROJECT.supabase.co",
7 "YOUR_SUPABASE_ANON_KEY",
8)
9 
10# 挂上 ModelRiver 客户端
11ai = OpenAI(
12 base_url="https://api.modelriver.com/v1",
13 api_key="mr_live_YOUR_API_KEY",
14)

将嵌入数据写入数据库 (Store embeddings)

PYTHON
1def store_document(content: str, metadata: dict = {}):
2 """利用 ModelRiver 将内容转为嵌入式并保存进 Supabase。"""
3 # 抽取并生成文档项的表示向量
4 response = ai.embeddings.create(
5 model="my-embedding-workflow",
6 input=[content],
7 )
8 embedding = response.data[0].embedding
9 
10 # 给其入库写档至 Supabase 中
11 supabase.table("documents").insert({
12 "content": content,
13 "metadata": metadata,
14 "embedding": embedding,
15 }).execute()
16 
17# 测试将文本保存
18store_document("ModelRiver routes AI requests across providers.", {"source": "docs"})
19store_document("Workflows configure provider and fallback settings.", {"source": "docs"})
20store_document("Structured outputs guarantee JSON compliance.", {"source": "docs"})

语义检索大搜底以及 RAG 系统实现 (Semantic search + RAG)

首先需要在上端 Supabase 创建建立一个对应远程程序查问搜索函数模块:

SQL
1-- Supabase SQL
2create or replace function match_documents(
3 query_embedding vector(1536),
4 match_count int default 5,
5 match_threshold float default 0.7
6)
7returns table (id bigint, content text, metadata jsonb, similarity float)
8language plpgsql
9as $$
10begin
11 return query
12 select
13 documents.id,
14 documents.content,
15 documents.metadata,
16 1 - (documents.embedding <=> query_embedding) as similarity
17 from documents
18 where 1 - (documents.embedding <=> query_embedding) > match_threshold
19 order by documents.embedding <=> query_embedding
20 limit match_count;
21end;
22$$;

编写基于 Python 的实现请求

PYTHON
1def ask(question: str, top_k: int = 3) -> str:
2 """结合语义查询搜索内容以及 AI 在基础层作发文生应答。"""
3 # 让此要问查询出的结果向量表示大化
4 query_embedding = ai.embeddings.create(
5 model="my-embedding-workflow",
6 input=[question],
7 ).data[0].embedding
8 
9 # 去叫寻 Supabase 并取搜索果实
10 results = supabase.rpc("match_documents", {
11 "query_embedding": query_embedding,
12 "match_count": top_k,
13 }).execute()
14 
15 # 排版结为关联查询大语料文字环境背景
16 context = "\n\n".join([r["content"] for r in results.data])
17 
18 # 输生成最结尾的发话终落响应回应
19 response = ai.chat.completions.create(
20 model="my-chat-workflow",
21 messages=[
22 {"role": "system", "content": f"依据语境下作答:\n\n{context}"},
23 {"role": "user", "content": question},
24 ],
25 )
26 
27 return response.choices[0].message.content
28 
29print(ask("How does ModelRiver handle failover?"))

接入向 JavaScript / Next.js 开去挂件

TYPESCRIPT
1import { createClient } from "@supabase/supabase-js";
2import OpenAI from "openai";
3 
4const supabase = createClient(
5 process.env.SUPABASE_URL!,
6 process.env.SUPABASE_ANON_KEY!,
7);
8 
9const ai = new OpenAI({
10 baseURL: "https://api.modelriver.com/v1",
11 apiKey: process.env.MODELRIVER_API_KEY!,
12});
13 
14async function ask(question: string): Promise<string> {
15 const { data: embeddingData } = await ai.embeddings.create({
16 model: "my-embedding-workflow",
17 input: [question],
18 });
19 
20 const { data: docs } = await supabase.rpc("match_documents", {
21 query_embedding: embeddingData[0].embedding,
22 match_count: 3,
23 });
24 
25 const context = docs.map((d: any) => d.content).join("\n\n");
26 
27 const response = await ai.chat.completions.create({
28 model: "my-chat-workflow",
29 messages: [
30 { role: "system", content: `依据语境回答:\n\n${context}` },
31 { role: "user", content: question },
32 ],
33 });
34 
35 return response.choices[0].message.content!;
36}

最佳实践 (Best practices)

  1. 如若您的记录已大于过数万点 (也就是万数行时):务必开挂加护启用使用 IVFFlat 或 HNSW 技术类下层大库护索。
  2. 切记让在基底挂在维度生成这端表长上的维长能够对对挂齐
  3. 连将本该随在被切段了这文字与段表数据也存并在落本旁 以让供取随存调起之大需
  4. 把自带上可做列排的 Row Level Security 安全级别过滤挂落开启
  5. 别忘上 可观测日志监控页面 (Request Logs):去观察了解生成的用工耗字钱以及监控它。

下一步您可以去到