Prompt 工程与 PGvector RAG 知识库:从文档 ETL 到查询增强的完整闭环

Prompt 工程与 PGvector RAG 知识库:从文档 ETL 到查询增强的完整闭环

Ethan
2025-11-01 发布 / 正在检测是否收录...

RAG 不是一个"搭起来就能用"的系统。从文档的清洗、切片、向量化,到检索时的重排序、查询增强,每一个环节都决定了最终回答的质量。本文将带你构建基于 PGvector 的生产级 RAG 知识库。

为什么选择 PGvector?

PGvector 是 PostgreSQL 的向量扩展,核心优势:向量和业务数据在同一张表——不需要单独维护向量数据库集群;可利用 PostgreSQL 的行级安全、事务、备份机制;支持混合检索(向量相似度 + SQL 条件过滤)。百万级以上纯向量检索场景,Milvus 和 Qdrant 更高效。

文档 ETL Pipeline

// Step 1: 文档加载
DocumentLoader loader = DocumentLoader.create()
    .registerSource(new FileSystemSource("docs/", "*.md"))
    .registerSource(new DatabaseSource(dataSource, "SELECT id, content FROM kb"));

// Step 2: 文档清洗
DocumentCleaner cleaner = DocumentCleaner.create()
    .addFilter(new RemoveEmptyLinesFilter())
    .addFilter(new RemoveHTMLTagsFilter())
    .addFilter(new MarkdownCodeBlockPreserver());

// Step 3: 智能切片——按语义边界而非字符数
SemanticChunker chunker = SemanticChunker.builder()
    .embeddingModel(embeddingModel)
    .maxChunkSize(1000)
    .minChunkSize(200)
    .overlapSize(100)
    .breakpointThreshold(0.7)    // 相似度低于此值才切分
    .build();

// Step 4: 元数据增强
chunk.setMetadata(Map.of(
    "source", "docs/java-virtual-threads.md",
    "section", "## 性能对比",
    "lastUpdated", "2025-10-20"
));

PGvector 表结构与索引

CREATE EXTENSION vector;

CREATE TABLE document_chunks (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    content TEXT NOT NULL,
    embedding VECTOR(1536),           -- OpenAI ada-002: 1536 维
    source VARCHAR(500),
    chunk_index INTEGER,
    metadata JSONB,
    created_at TIMESTAMP DEFAULT now()
);

-- HNSW 索引(写入和查询都比 IVFFlat 快)
CREATE INDEX ON document_chunks
  USING hnsw (embedding vector_cosine_ops)
  WITH (m = 16, ef_construction = 200);

-- 混合查询:语义 + 关键词 + 元数据过滤
SELECT content,
       1 - (embedding <=> query_embedding) AS similarity,
       ts_rank(to_tsvector('chinese', content),
               plainto_tsquery('chinese', '虚拟线程 性能')) AS keyword_score
FROM document_chunks
WHERE metadata->>'source' LIKE '%java%'
  AND 1 - (embedding <=> query_embedding) > 0.75
ORDER BY similarity * 0.7 + COALESCE(keyword_score, 0) * 0.3 DESC
LIMIT 5;

查询增强(Query Enhancement)

// 1. 查询改写(Query Rewriting)
// 用户:"那个线程的东西怎么用?"
// 改写:"Java 虚拟线程 VirtualThread 使用方法"

// 2. 查询扩展(Query Expansion)
// "Spring Cloud" → ["Spring Cloud", "微服务", "Nacos", "Gateway"]

// 3. HyDE(Hypothetical Document Embedding)
// 先让 LLM 生成假设的完美答案,用答案做向量检索
String hypothetical = llm.generate("请假设性回答:Java 虚拟线程的核心优势?");
List<Document> results = vectorStore.search(hypothetical);

检索后处理:ReRank

// 向量检索返回 Top-20,用 ReRanker 精选 Top-5
List<Document> candidates = vectorStore.search(query, 20);
Reranker reranker = new BgeReranker();
List<Document> reranked = reranker.rerank(query, candidates, 5);
// Cross-Encoder 比 Bi-Encoder(向量相似度)更精确

Prompt 工程进阶

结构化 Prompt 模板

String prompt = """
    # 角色定义
    你是一个专业的技术文档助手,擅长回答 Java 和 Spring 相关问题。

    # 安全规则
    - 如果参考资料中没有相关信息,请明确说明
    - 不要编造任何不存在的 API、参数或版本号

    # 参考资料(按相关性排序)
    {context}

    # 输出格式
    1. 先给出简洁的答案(1-3句话)
    2. 然后提供代码示例(如果有)
    3. 最后列出注意事项

    # 用户问题
    {query}
    """;

CoT(Chain of Thought)

String cotPrompt = """
    请逐步思考:
    1. 这个问题涉及哪些技术概念?
    2. 这些概念之间的关联是什么?
    3. 最佳实践是什么?
    4. 有没有常见的坑?
    请在每个步骤后说明推理过程,然后给出最终答案。
    """;

Few-Shot Prompting

"""
示例1:
Q: Spring Boot 怎么连接 MySQL?
A: [配置] application.yml 中添加 datasource... [代码] @Autowired DataSource... [注意] driver-class-name 用 com.mysql.cj.jdbc.Driver

示例2:
Q: 什么是 AOP?
A: [定义] AOP 面向切面编程... [原理] 动态代理... [使用] @Aspect + @Around...

现在回答:{query}
""";

总结

RAG 的质量由ETL Pipeline 质量 + 检索策略精度 + Prompt 设计水准共同决定。一个"能用"的 RAG 只需 100 行代码,一个"好用"的 RAG 需要对每个环节精细调优。Prompt 工程是最后一道关卡——Context 再好,Prompt 不好,回答也会跑偏。

© 版权声明
THE END
喜欢就支持一下吧
点赞 2 分享 收藏

评论 (0)

取消

Warning: file_put_contents(/var/www/html/usr/cache/pagecache/0e/0e6b7db654b1f01002192025619f2439.cache): failed to open stream: No such file or directory in /var/www/html/usr/plugins/PageCache/Plugin.php on line 188