欢迎光临
我们一直在努力

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

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

RAG是什么?为什么它是"AI产品的核心架构"

RAG(Retrieval-Augmented Generation,检索增强生成)是解决LLM(大语言模型)"幻觉"和"知识过时"问题的核心技术。

问题一:LLM的幻觉(Hallucination)

LLM会"编造"它不知道的信息。如果你问"你们产品的退款政策是什么?",LLM可能生成一个"听起来合理但实际不存在"的退款政策——这会导致用户投诉。

问题二:LLM的知识截止日期

GPT-4的知识截止到2023年12月,Claude 3的截止到2024年某月。如果你的产品有"最新功能更新",LLM不知道。

RAG的解决方案:给LLM提供"外部知识库"

RAG的流程:

  • 检索(Retrieve):根据用户问题,从"知识库"里找到最相关的文档
  • 增强(Augment):把检索到的文档,作为"上下文"提供给LLM
  • 生成(Generate):LLM基于"上下文"回答问题——不再编造
  • 技术栈选型:Vector DB + Embedding Model + LLM

    Vector Database(向量数据库):存储和检索"文档的向量表示"

    主流选择:

    • Pinecone:托管服务,免费计划(5万向量),简单易用
    • Weaviate:开源,可自托管,也可托管
    • Qdrant:开源,Rust编写,性能极高,适合自托管
    • Supabase pgvector:如果你已经用Supabase,可以直接用它的pgvector扩展(免费)

    Embedding Model(嵌入模型):把文本转换成向量

    • OpenAI text-embedding-3-small:性能好,价格低($0.02/1M tokens)
    • Cohere 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")。

    第二步:文档分块(Chunking)

    LLM的上下文窗口有限(即使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): Promise<string> {
    // 1. 检索相关文档
    const relevantDocs = await retrieveRelevantDocs(question);

    if (relevantDocs.length === 0) {
    return "抱歉,我在知识库里没有找到相关信息。请联系support@yourproduct.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): Promise<string> {
    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 Accuracy):LLM的回答是否正确?(人工评估,或用"有标准答案的测试集"自动评估)
  • 拒答率(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;
    }

    监控:

    用LangSmith(LangChain的监控平台)或Helicone监控:

    • 每次检索的"相似度分数"分布
    • LLM的"Token消耗"和"响应时间"
    • 用户反馈(点赞/点踩)

    结论:RAG是独立开发者的"AI产品入场券"

    你不需要训练自己的LLM(成本高、技术难度大)。用RAG架构,你可以用"少量文档 + 开源/商业化向量数据库 + API调用的LLM",构建出"高质量、低成本"的AI功能。

    最小可行RAG(MVP):

  • 用Supabase pgvector存储文档向量(免费)
  • 用OpenAI text-embedding-3-small生成向量($0.02/1M tokens,很便宜)
  • 用GPT-4o生成回答($0.005/1K input tokens)
  • 这套方案,处理1万次查询的成本约$20-50——对独立开发者完全可承受。

    下一步: 把RAG应用到"客户支持自动化"、"产品推荐"、"个性化学习路径"等场景——这些是"AI时代独立开发者的核心竞争优势"。

    赞(0)
    未经允许不得转载:171主机测评 » 用Vector Database + LLM构建RAG应用:独立开发者的AI产品实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址