ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用 Portkey + Supabase pgvector 构建支持文章智能推荐应用:完整实战指南

用 Portkey + Supabase pgvector 构建支持文章智能推荐应用:完整实战指南 用 Portkey Supabase pgvector 构建支持文章智能推荐应用完整实战指南【免费下载链接】gatewayA blazing fast AI Gateway with integrated guardrails. Route to 1,600 LLMs, 50 AI Guardrails with 1 fast friendly API.项目地址: https://gitcode.com/GitHub_Trending/ga/gateway导读本文基于开源仓库 GatewayPortkey AI Gateway的官方教程逐步实现一个 NodeJS 应用把一批“支持文章标题”通过 OpenAI 嵌入模型转化为向量并存入 Supabase 的 pgvector 扩展当用户输入新查询时借助数据库函数完成向量相似度检索并返回最匹配的文章。读完本文你将掌握embedding 与向量检索的核心概念、Portkey 虚拟密钥Virtual Key的接入方式、pgvector 建表与相似度匹配函数的完整 SQL、以及如何用 Gateway 的/v1/embeddings接口对接底层实现。什么是向量相似度搜索它为何特殊简短的回答是Embeddings嵌入向量。将一段内容翻译成向量表示的技术称为嵌入Embeddings。它允许你用数学方式分析内容的语义——LLM 能够把内容转化为向量表示并嵌入到向量空间中两条内容的相似程度由它们向量之间的距离来决定。这些向量需要被存储到向量数据库中本文选用 Supabase 并启用其pgvector扩展来存储向量。嵌入向量示意内容 → 向量 → 向量空间说明本文旨在为“支持文章/文档推荐、搜索建议”这类问题提供一个可复现的起点与导航地图请结合你的实际业务结构调整字段与阈值。应用总览整体工作流程我们的应用利用 Supabase 向量数据库以 embedding 形式维护文章收到用户新查询后由数据库智能推荐最相关的文章。完整处理流程分为四步应用读取一个包含文章标题列表的文本文件通过Portkey调用 OpenAI 模型将内容转换为 embedding将 embedding 存入pgvector并创建一个支持相似度匹配的数据库函数当用户输入新查询时应用基于该数据库函数返回最相关的文章。环境准备Portkey、Supabase、NodeJS 三件事开始编码前先完成三个环境的配置。Portkey创建虚拟密钥Virtual Key注册并登录 Portkey 控制台复制你的 OpenAI API Key将其添加到 Portkey Vault虚拟密钥。这会为你生成一个唯一标识符——虚拟密钥Virtual Key后续代码中可直接引用它而无需在应用里明文保存 OpenAI 的原始 Key。从仓库源码可以看到Gateway 在构建请求时会识别virtual_key这类标识见 modelsHandler.ts 中HEADER_KEYS.VIRTUAL_KEY的处理并在 handlerUtils.ts 中通过virtualKeyDetails解析密钥明细实现密钥的托管与复用。Supabase创建项目并启用 Vector 扩展进入 Supabase 创建一个新项目示例中命名为 “Product Wiki”。创建完成后会得到访问密钥包括Project URL与API Key请保存好。项目就绪后为了让数据库能存储 embedding必须在Dashboard Database Extensions中启用Vector扩展。NodeJS初始化项目与文章数据进入任意目标目录初始化项目npm init -y查看生成的package.json。由于需要把文章列表存入数据库先创建articles.txt并复制以下内容10 条典型支持文章标题Update Your Operating System Resetting Your Password Maximizing Battery Life Cleaning Your Keyboard Protecting Against Malware Backing Up Your Data Troubleshooting Wi-Fi Issues Optimizing Your Workspace Understanding Cloud Storage Managing App Permissions打开index.js开始编写代码。Step 1导入并认证 Portkey 与 Supabase应用需要与 OpenAI经由 Portkey和 Supabase pgvector 数据库交互因此先导入必要的 SDK 客户端import { Portkey } from portkey-ai; import { createClient } from supabase/supabase-js; import fs from fs; const USER_QUERY How to update my laptop?; const supabase createClient(https://rbhjxxxxxxxxxkr.supabase.co, process.env[SUPABASE_PROJECT_API_KEY]); const portkey new Portkey({ apiKey: process.env[PORTKEY_API_KEY], virtualKey: process.env[OPENAI_VIRTUAL_KEY] });fs用于从articles.txt读取文章列表USER_QUERY是稍后做相似度搜索用的用户查询supabase客户端通过Project URL API Key认证portkey客户端使用Portkey API Key 虚拟密钥认证虚拟密钥决定实际路由到哪个上游这里是 OpenAI。在 Gateway 仓库中OpenAI 嵌入接口的鉴权与参数解析由 openai/embed.ts 的OpenAIEmbedConfig定义它把model、input、encoding_format、dimensions、user等参数映射到 OpenAI 上游请求其中model默认值正是text-embedding-ada-002。Step 2在 Supabase 中创建数据表使用 Supabase 的 SQL Editor 执行 SQL。本项目只需一张表support_articles存储文章的title及其 embedding。你可以按需增加字段如 description、tags。为简单起见创建包含ID、content、embedding三列的表create table support_articles ( id bigint primary key generated always as identity, content text, embedding vector (1536) );在 SQL Editor 中执行上述语句执行成功后可在Database Tables support_articles中验证表已创建Results 标签页会出现成功提示。维度说明vector (1536)的维度必须与所用嵌入模型输出的向量维度一致。text-embedding-ada-002输出 1536 维向量因此建表时声明vector(1536)。若更换模型例如输出 3072 维的text-embedding-3-large需同步修改建表与函数中的维度声明。Step 3读取文章、生成并存储 Embeddings使用fs读取articles.txt将每一行标题转换为 embedding。借助 Portkey生成 embedding 的写法与直接使用 OpenAI SDK 完全一致无需额外代码改动——这也是通过 AI Gateway 接入多个模型时的最大便利。生成 embedding 的核心调用const response await portkey.embeddings.create({ input: String(text), model: text-embedding-ada-002 }); return Array.from(response.data[0].embedding);存储到 Supabaseawait supabase.from(support_articles).insert({ content, embedding });把「读文件 → 生成 embedding → 写入 Supabase」整合在一起async function convertToEmbeddings(text) { const response await portkey.embeddings.create({ input: String(text), model: text-embedding-ada-002 }); return Array.from(response.data[0].embedding); } async function readTitlesFromFile() { const titlesPath ./articles.txt; const titles fs .readFileSync(titlesPath, utf8) .split(\n) .map((title) title.trim()); return titles; } async function storeSupportArticles() { const titles await readTitlesFromFile(); titles.forEach(async function (title) { const content title; const embedding await convertToEmbeddings(content); await supabase.from(support_articles).insert({ content, embedding }); }); }就这些——只需一行调用即可把所有条目写入 pgvector 数据库await storeSupportArticles();现在可以从 Table Editor 看到创建的行补充响应结构里能拿到什么portkey.embeddings.create返回的响应结构与 OpenAI 一致仓库 embedRequestBody.ts 的EmbedResponse类型给出了完整字段object响应对象类型如listdata嵌入结果数组每项含object、embedding向量数组、indexmodel实际使用的嵌入模型usage包含prompt_tokens与total_tokens可用于成本与用量统计。这解释了为什么代码中用response.data[0].embedding就能取到第一个输入文本的 1536 维向量。Step 4创建数据库函数以查询相似匹配接下来在 Supabase 中创建一个数据库函数做向量相似度搜索。该函数接收用户查询向量作为参数返回与用户查询最匹配的行包含id、content和相似度分数similaritycreate or replace function match_documents ( query_embedding vector(1536), match_threshold float, match_count int ) returns table ( id bigint, content text, similarity float ) language sql stable as $$ select support_articles.id, -- documents here is the table name support_articles.content, 1 - (support_articles.embedding query_embedding) as similarity -- is cosine similarity search from support_articles where 1 - (support_articles.embedding query_embedding) match_threshold order by (support_articles.embedding query_embedding) asc limit match_count; $$;在 SQL Editor 中执行与建表步骤相同恭喜现在support_articles表已具备返回向量相似度搜索结果的能力。关键点拆解是 pgvector 提供的余弦距离运算符1 - 余弦距离即余弦相似度值越接近 1 表示越相似match_threshold用于过滤相似度低于阈值的低质量匹配match_count限制返回的行数language sql stable声明该函数是稳定的 SQL 函数不写库、结果确定便于优化器使用。Step 5查询相似度匹配Supabase 客户端可以通过远程过程调用RPC调用match_documents函数找到与用户查询最接近的匹配async function findNearestMatch(queryEmbedding) { const { data } await supabase.rpc(match_documents, { query_embedding: queryEmbedding, match_threshold: 0.5, match_count: 1 }); return data; }传入的参数与 Step 4 中声明函数时的形参一一对应query_embedding查询向量、match_threshold相似度阈值示例为 0.5、match_count返回条数示例为 1。组装完整执行流程const USER_QUERY How to update my laptop?; // Invoke the following Fn to store embeddings to Supabase // await storeSupportArticles(); const queryEmbedding await convertToEmbeddings(USER_QUERY); let best_match await findNearestMatch(queryEmbedding); console.info(The best match is: , best_match);控制台输出The best match is: [ { id: 12, content: Update Your Operating System, similarity: 0.874387819265234 } ]用户查询How to update my laptop?与Update Your Operating System的余弦相似度高达约 0.874成功命中预期文章。观察与监控一次查询的成本与延迟针对上述用户查询单次查询含 embedding 生成消耗约 6 tokens成本约为 $0.0001。整个开发过程中共消耗约 2.4k tokens平均延迟 383ms。这些数据全部来自Portkey Dashboard的可观测能力这类信息在生产环境实时监控中极具价值。建议你在进行中的项目里落地搜索类用例——推荐、建议、FAQ 匹配等场景都可复用本文方案。至此你已经掌握了如何在开发中使用 embedding、并在生产环境中监控应用。原理纵深Gateway 中/v1/embeddings的底层实现上述教程通过portkey-aiSDK 直连 Portkey 云服务。而在本开源仓库Gateway中同样能力以自托管网关形式提供其实现细节与教程完全同源1. 路由注册网关在 src/index.ts 注册了POST /v1/embeddings路由经requestValidator校验后交由embeddingsHandler处理与chat/completions、completions等端点并列。2. 请求处理链embeddingsHandler.ts 先解析 JSON 请求体与请求头通过constructConfigFromRequestHeaders从x-portkey-*头中还原 provider 配置再调用tryTargetsRecursively见 handlerUtils.ts按目标组递归尝试路由——这也正是网关支持多 provider、多目标、失败回退与重试的基础。3. 参数映射openai/embed.ts 的OpenAIEmbedConfig声明了 OpenAI 嵌入接口的参数契约model必填默认text-embedding-ada-002、input必填、可选的encoding_format、dimensions、user。请求体结构EmbedRequestBodyconfigparams与响应结构EmbedResponse定义在 embedRequestBody.ts。这意味着教程中portkey.embeddings.create({ input, model })的调用方式与向自托管 Gateway 发起POST /v1/embeddings的请求语义完全一致生产环境可直接把 SDK 指向自部署网关实现密钥托管、负载均衡、缓存与可观测性的统一。小结与延伸通过本文你已经完成了一条完整的“文本 → 向量 → 检索”链路环节技术选型关键动作内容向量化OpenAI经 Portkey/虚拟密钥portkey.embeddings.create向量存储Supabase pgvector建表vector(1536)插入 embedding相似度检索数据库函数 RPCmatch_documentssupabase.rpc监控Portkey Dashboard观察 token、延迟、成本从仓库源码index.ts、embeddingsHandler.ts、openai/embed.ts可以看到这套能力在 Gateway 中是一等公民 API可直接迁移到生产环境自托管部署。后续你还可以尝试把articles.txt替换为真实文档库并增加 description 字段用text-embedding-3-large等更高维模型提升精度记得同步修改vector(1536)与函数签名在网关上叠加缓存与限流配置进一步优化查询成本。【免费下载链接】gatewayA blazing fast AI Gateway with integrated guardrails. Route to 1,600 LLMs, 50 AI Guardrails with 1 fast friendly API.项目地址: https://gitcode.com/GitHub_Trending/ga/gateway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表