检索增强生成(RAG)已经成为给大模型应用添加知识库的标准做法。用户提问时,系统从你的数据里找出相关片段,连同问题一起丢给模型,模型基于你提供的上下文作答。
但大多数教程一上来就搬出LangChain。这篇指南跳过框架,从零开始搭建完整流水线:pgvector负责向量存储,OpenAI Python SDK处理嵌入和生成,psycopg对接数据库。最终代码不到200行。
数据库设计:三张表搞定核心架构
pgvector是PostgreSQL的向量扩展,把向量检索直接塞进关系数据库。不需要额外维护一套专用向量库,备份、权限、事务都能复用现有基础设施。
表结构很直白。documents表存文档元数据:标题和来源。chunks表存切片后的文本块,每个块带1536维向量,用HNSW索引加速相似度搜索。索引参数设成m=16、ef_construction=64,在构建速度和召回率之间取平衡。
这里有个细节:token_count字段预留了,但实际代码里用的是len(chunk.split())做简单估算。生产环境建议换用tiktoken精确计算,避免超出模型上下文窗口。
文本切片:滑动窗口策略
chunk_text函数实现了带重叠的滑动窗口。默认每块400词、重叠50词。重叠设计是为了防止关键信息正好落在边界被切断。比如"2024年第三季度营收增长"如果切成"2024年第三季度"和"营收增长",语义就碎了。
嵌入环节做了批处理优化。OpenAI的嵌入接口单次最多接受256条输入,代码里用range(0, len(chunks), 256)分批处理,避免超长列表被截断或触发速率限制。
模型选的是text-embedding-3-small,1536维输出,成本和性能的平衡点。如果预算充裕且对精度敏感,可以换成text-embedding-3-large,3072维,但价格翻几倍。
检索逻辑:余弦相似度的SQL实现
retrieve_chunks函数是核心。先把问题转成向量,然后执行这条SQL:
SELECT c.content, d.title, 1 - (c.embedding <=> %s::vector) AS similarity...
<=>是pgvector的余弦距离运算符,返回0到2之间的值(0表示完全相同)。用1减去距离得到相似度分数,方便人类阅读。JOIN操作把文本块和原始文档关联,输出时带上来源标题,方便追溯。
limit=5默认返回前5个最相关块。这个值需要根据实际场景调:知识库密集、答案分散时加大,文档结构清晰、答案集中时减小,省token。
生成环节:强制约束模型
answer_question函数把检索结果格式化成上下文块,用---分隔,前面标注来源。system prompt很直接:"Answer using only this context",禁止模型调用预训练知识瞎编。
temperature=0.2压低随机性,适合事实性问答。如果要做创意写作或头脑风暴,可以调高到0.7-0.9。
模型选gpt-4o-mini,便宜、快、够用。上下文窗口128K,但这里检索结果通常几千token,远未触顶。
为什么选pgvector,而不是专用向量数据库?
这是架构上最关键的取舍。Pinecone、Weaviate、Milvus这些专用方案在超大规模场景确实有优势,但大多数应用根本到不了那个量级。
pgvector的好处是减少技术栈复杂度。你的用户数据、业务数据、向量数据全在一个库里,JOIN查询不用跨服务,事务能保证一致性,备份策略统一。团队如果已经有PostgreSQL经验,学习成本几乎为零。
性能方面,HNSW索引在百万级向量上查询延迟通常在10-50毫秒,对交互式应用足够快。真到千万级再考虑分片或迁移也不迟——向量导出导入比想象中还简单。
这套代码缺什么?
生产部署至少还要补几块:重排序(reranking),用更精确的模型对初筛结果二次打分;查询重写,把口语化问题扩展成更利于检索的表述;缓存层,常见问题的嵌入结果和答案直接命中;监控,追踪检索命中率、答案相关性、延迟分布。
但这些增量改进,在200行骨架上扩展,比从LangChain的抽象层里扒拉出来要直观得多。
热门跟贴