# ragDemo **Repository Path**: tfxing12138/rag-demo ## Basic Information - **Project Name**: ragDemo - **Description**: 使用pgsql+pgvector实现向量存储和向量查询 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-01-29 - **Last Updated**: 2026-01-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 使用 PostgreSQL + pgvector 实现 RAG 向量存储与语义检索(Java 实战) 在 RAG(Retrieval-Augmented Generation)系统中,**向量存储与相似度检索**是最核心的一环。 本文将使用 **PostgreSQL + pgvector**,结合 **Java + 阿里百炼 Embedding 模型**,实现一个完整、可运行的向量存储与语义检索示例。 > 本文不仅关注“能跑”,更重点解释 **为什么这么设计** 以及 **RAG 中最容易踩的坑**。 --- ## 一、pgvector 是什么? `pgvector` 是 PostgreSQL 的一个扩展插件,为 PostgreSQL 提供了专门的 **vector 类型**,用于存储高维向量,并支持: * 余弦距离(cosine distance) * 欧氏距离(L2) * 内积(inner product) * 向量索引(ivfflat / hnsw) 这使得 PostgreSQL 可以直接作为 **向量数据库** 使用,非常适合中小规模 RAG 场景。 --- ## 二、安装 PostgreSQL 与 pgvector 安装过程不再赘述。 👉 可参考 CSDN 博主 **进击的女IT** 的文章: [https://blog.csdn.net/weixin\_63908159/article/details/156075242](https://blog.csdn.net/weixin_63908159/article/details/156075242) --- ## 三、建表与索引设计(非常重要) ### 1️⃣ 建表语句 ```sql CREATE TABLE document ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, embedding vector(1024), create_time TIMESTAMP DEFAULT now() ); ``` > ⚠️ **注意** > `vector(1024)` 必须与 Embedding 模型输出的向量维度一致。如果模型输出是 1536 维,这里必须改成 `vector(1536)`,否则插入时会报错:`expected xxx dimensions`。 ### 2️⃣ 向量索引(否则数据一多会非常慢) ```sql CREATE INDEX idx_document_embedding ON document USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); ``` 查询前建议设置: ```sql SET ivfflat.probes = 10; ``` --- ## 四、Java 实体类设计 `pgvector` 在 Java 中无需特殊类型映射,直接使用 `String` 承载即可。 ```java @Data public class Document { private Long id; private String content; /** * pgvector 字段 * 使用 String 承载,例如:[0.12,0.34,...] */ private String embedding; private LocalDateTime createTime; } ``` --- ## 五、Embedding 模型配置(阿里百炼) 配置文件 (`application.yml`): ```yml alibaba: dashscope: key: sk-xxx url: https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings model: text-embedding-v4 ``` 配置类: ```java @Configuration @ConfigurationProperties(prefix = "alibaba.dashscope") @Data public class AlibabaDashscopeConfig { private String key; private String url; private String model; } ``` --- ## 六、文本向量化服务(EmbeddingService) 功能:文本 → 向量 (`List`) ```java @Service @Slf4j public class EmbeddingService { @Autowired private AlibabaDashscopeConfig config; public List getEmbedding(String text) { if (StrUtil.isBlank(text)) { return List.of(); } Map body = Map.of( "model", config.getModel(), "input", List.of(text) ); HttpResponse response = HttpRequest.post(config.getUrl()) .header("Authorization", "Bearer " + config.getKey()) .header("Content-Type", "application/json") .body(JSONUtil.toJsonStr(body)) .timeout(30000) .execute(); JSONObject json = JSONUtil.parseObj(response.body()); JSONArray embedding = json.getJSONArray("data") .getJSONObject(0) .getJSONArray("embedding"); return embedding.toList(Float.class); } } ``` --- ## 七、向量存储与相似度查询 ### Mapper ```java @Mapper public interface DocumentMapper extends BaseMapper { /** * 向量相似度搜索(余弦距离) */ @Select(""" SELECT id, content FROM document ORDER BY embedding <=> #{embedding}::vector LIMIT #{limit} """) List searchByEmbedding( @Param("embedding") String embedding, @Param("limit") int limit ); @Insert(""" INSERT INTO document (content, embedding) VALUES (#{content}, #{embedding}::vector) """) void insertDocument( @Param("content") String content, @Param("embedding") String embedding ); } ``` ### Service ```java @Service @RequiredArgsConstructor public class DocumentService { private final DocumentMapper mapper; private final EmbeddingService embeddingService; public void addDocument(String content) { List vector = embeddingService.getEmbedding(content); String pgVector = vector.stream() .map(String::valueOf) .collect(Collectors.joining(",", "[", "]")); mapper.insertDocument(content, pgVector); } public List search(String query, int topK) { List vector = embeddingService.getEmbedding(query); if (vector.isEmpty()) { return List.of(); } String pgVector = vector.stream() .map(String::valueOf) .collect(Collectors.joining(",", "[", "]")); return mapper.searchByEmbedding(pgVector, topK); } } ``` --- ## 八、Controller 接口 ```java @RestController @RequestMapping("/document") @RequiredArgsConstructor public class DocumentController { private final DocumentService service; @GetMapping("/add") public void add(@RequestParam String content) { service.addDocument(content); } @GetMapping("/search") public List search( @RequestParam String query, @RequestParam(defaultValue = "5") int topK ) { return service.search(query, topK); } } ``` --- ## 九、为什么“最近流行什么”查询不到结果? 这是语义检索中最容易被误解的一点: > **向量检索 ≠ 关键词匹配** 向量检索判断的是 **语义是否在同一语义空间**。 **示例:** | 文本 | 语义中心 | |:----------------- |:------- | | Java 是一门流行的后端开发语言 | 编程 / 后端 | | 最近流行什么 | 趋势 / 热点 | 👉 两者在语义空间中的距离很远,因此检索不到是正常且正确的行为。 **正确做法:** 1. Query Rewrite(查询重写) 2. 补充领域上下文 例如: > 最近流行的后端开发语言有哪些? --- ## 十、总结 * `pgvector` 可以让 PostgreSQL 直接作为向量数据库使用。 * 向量检索本质是 **语义相似度计算**。 * RAG 的效果高度依赖于: * 文档内容的表达方式 * Query 是否足够具体 * 相似度阈值与 TopK 的设计 --- 📌 **项目完整源码地址(Gitee)** [https://gitee.com/tfxing12138/rag-demo.git](https://gitee.com/tfxing12138/rag-demo.git)