用Vector Database + LLM构建RAG应用:独立开发者的AI产品实战

用Vector Database + LLM构建RAG应用:独立开发者的AI产品实战 用Vector Database LLM构建RAG应用独立开发者的AI产品实战RAG是什么为什么它是AI产品的核心架构RAGRetrieval-Augmented Generation检索增强生成是解决LLM大语言模型幻觉和知识过时问题的核心技术。问题一LLM的幻觉HallucinationLLM会编造它不知道的信息。如果你问你们产品的退款政策是什么LLM可能生成一个听起来合理但实际不存在的退款政策——这会导致用户投诉。问题二LLM的知识截止日期GPT-4的知识截止到2023年12月Claude 3的截止到2024年某月。如果你的产品有最新功能更新LLM不知道。RAG的解决方案给LLM提供外部知识库RAG的流程检索Retrieve根据用户问题从知识库里找到最相关的文档增强Augment把检索到的文档作为上下文提供给LLM生成GenerateLLM基于上下文回答问题——不再编造技术栈选型Vector DB Embedding Model LLMVector Database向量数据库存储和检索文档的向量表示主流选择Pinecone托管服务免费计划5万向量简单易用Weaviate开源可自托管也可托管Qdrant开源Rust编写性能极高适合自托管Supabase pgvector如果你已经用Supabase可以直接用它的pgvector扩展免费Embedding Model嵌入模型把文本转换成向量OpenAI text-embedding-3-small性能好价格低$0.02/1M tokensCohere embed-english-v3.0多语言支持好开源方案all-MiniLM-L6-v2本地运行无API成本LLM回答生成GPT-4o/GPT-4-turbo质量最高价格中等Claude 3.5 Sonnet长上下文200K tokens适合大量文档场景开源方案Llama 3用Groq或自托管成本更低实战用Supabase pgvector OpenAI构建产品文档问答机器人第一步准备知识库文档把你的产品文档、FAQ、帮助文档整理成Markdown文件。每个文件应该是一个独立的主题如如何退款.md、如何更改定价计划.md。第二步文档分块ChunkingLLM的上下文窗口有限即使200K也不能把整个知识库都塞进去。需要把文档分块。分块策略按标题分块每个H2/H3章节作为一个块固定大小分块每块500-1000个字符重叠100-200字符避免切断语义按语义分块用LLM判断这段是否在讲同一个主题我的实现固定大小分块// lib/chunkDocuments.ts export function chunkDocument(text: string, chunkSize 800, overlap 200): string[] { const chunks: string[] []; let start 0; while (start text.length) { const end start chunkSize; const chunk text.slice(start, end); chunks.push(chunk); start chunkSize - overlap; // 重叠避免切断语义 } return chunks; }第三步生成向量并存储到pgvector// scripts/embedDocuments.ts import { OpenAI } from openai; import { createClient } from supabase/supabase-js; const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY! }); const supabase createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!); // 启用pgvector扩展在Supabase SQL编辑器里执行 // CREATE EXTENSION IF NOT EXISTS vector; // 创建表如果还没创建 // CREATE TABLE documents ( // id BIGSERIAL PRIMARY KEY, // content TEXT, // embedding VECTOR(1536), -- OpenAI embedding维度是1536 // metadata JSONB -- 存储标题、来源URL等 // ); async function embedAndStore() { // 1. 读取所有文档假设在docs/目录 const docs await readAllDocuments(./docs); for (const doc of docs) { const chunks chunkDocument(doc.content); for (let i 0; i chunks.length; i) { const chunk chunks[i]; // 2. 生成embedding const embeddingResp await openai.embeddings.create({ model: text-embedding-3-small, input: chunk, }); const embedding embeddingResp.data[0].embedding; // 3. 存储到pgvector await supabase.from(documents).insert({ content: chunk, embedding: [${embedding.join(,)}], // pgvector格式 metadata: { title: doc.title, source: doc.url, chunk_index: i, }, }); console.log(Stored chunk ${i} of ${doc.title}); } } }第四步实现检索相似度搜索// lib/retrieve.ts import { OpenAI } from openai; import { createClient } from supabase/supabase-js; const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY! }); const supabase createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!); export async function retrieveRelevantDocs(query: string, topK 5) { // 1. 把查询转换成向量 const embeddingResp await openai.embeddings.create({ model: text-embedding-3-small, input: query, }); const queryEmbedding embeddingResp.data[0].embedding; // 2. 在pgvector里做相似度搜索余弦距离 const { data, error } await supabase.rpc(match_documents, { query_embedding: [${queryEmbedding.join(,)}], match_threshold: 0.78, // 相似度阈值0-1越大越严格 match_count: topK, }); if (error) { console.error(Search error:, error); return []; } return data; // 返回相关的文档块 }在Supabase里创建match_documents函数CREATE OR REPLACE FUNCTION match_documents ( query_embedding VECTOR(1536), match_threshold FLOAT, match_count INT ) RETURNS TABLE ( id BIGINT, content TEXT, metadata JSONB, similarity FLOAT ) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT documents.id, documents.content, documents.metadata, 1 - (documents.embedding query_embedding) AS similarity -- 余弦相似度 FROM documents WHERE 1 - (documents.embedding query_embedding) match_threshold ORDER BY similarity DESC LIMIT match_count; END; $$;第五步构建Prompt并调用LLM// lib/rag.ts import { OpenAI } from openai; import { retrieveRelevantDocs } from ./retrieve; const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY! }); export async function answerWithRAG(question: string): Promisestring { // 1. 检索相关文档 const relevantDocs await retrieveRelevantDocs(question); if (relevantDocs.length 0) { return 抱歉我在知识库里没有找到相关信息。请联系supportyourproduct.com。; } // 2. 构建上下文 const context relevantDocs.map(doc doc.content).join(\n\n---\n\n); // 3. 构建Prompt const prompt 你是${process.env.PRODUCT_NAME}的帮助助手。基于以下上下文回答问题。如果上下文里没有答案说我不知道不要编造。 上下文 ${context} 问题${question} 回答; // 4. 调用LLM const resp await openai.chat.completions.create({ model: gpt-4o, messages: [ { role: system, content: 你是 helpful 的产品帮助助手。 }, { role: user, content: prompt }, ], temperature: 0.3, // 降低温度减少幻觉 max_tokens: 500, }); return resp.choices[0].message.content!; }评估与优化让RAG应用更准确常见问题一检索到的文档不相关原因Embedding模型没有理解领域专业术语。解决方案用领域微调的Embedding模型或在检索前改写用户问题用LLM把口语化问题转换成包含关键词的问题。async function rewriteQuery(originalQuery: string): Promisestring { const resp await openai.chat.completions.create({ model: gpt-4o, messages: [ { role: system, content: 你是查询优化助手。把用户的问题改写成包含关键词的查询用于向量数据库检索。 }, { role: user, content: originalQuery }, ], temperature: 0.3, }); return resp.choices[0].message.content!; }常见问题二LLM仍然编造答案原因上下文不够明确或LLM太自信。解决方案让LLM引用来源。// 在Prompt里要求引用来源 const prompt 基于以下上下文回答问题。每个回答后面用[来源: 文档标题]格式引用来源。如果上下文里没有说我不知道。 上下文 ${context} 问题${question} 回答;评估指标检索准确率Retrieval Precision检索到的Top-5文档里有多少是真的相关回答准确率Answer AccuracyLLM的回答是否正确人工评估或用有标准答案的测试集自动评估拒答率Abstain Rate当知识库没有答案时LLM是否说我不知道越高越好避免幻觉部署与监控让RAG应用稳定运行缓存常见查询用Redis缓存常见问题的回答。import { createClient } from redis; const redis createClient({ url: process.env.REDIS_URL! }); export async function answerWithCache(question: string) { // 尝试从缓存读取 const cached await redis.get(rag:${question}); if (cached) return cached; // 缓存未命中执行RAG const answer await answerWithRAG(question); // 写入缓存过期时间1小时 await redis.set(rag:${question}, answer, { EX: 3600 }); return answer; }监控用LangSmithLangChain的监控平台或Helicone监控每次检索的相似度分数分布LLM的Token消耗和响应时间用户反馈点赞/点踩结论RAG是独立开发者的AI产品入场券你不需要训练自己的LLM成本高、技术难度大。用RAG架构你可以用少量文档 开源/商业化向量数据库 API调用的LLM构建出高质量、低成本的AI功能。最小可行RAGMVP用Supabase pgvector存储文档向量免费用OpenAI text-embedding-3-small生成向量$0.02/1M tokens很便宜用GPT-4o生成回答$0.005/1K input tokens这套方案处理1万次查询的成本约$20-50——对独立开发者完全可承受。下一步把RAG应用到客户支持自动化、产品推荐、个性化学习路径等场景——这些是AI时代独立开发者的核心竞争优势。