作为Java后端开发者,你是不是也有这样的困惑:想接入AI大模型做智能问答,但要么模型“一本正经地胡说八道”,要么私有业务数据无法接入,要么不知道怎么适配具体模型?

其实不用慌,今天就给大家讲透「RAG技术」—— 解决大模型“失忆”“不懂业务”的神器,再手把手教你适配智谱GLM模型(国内主流、易接入、免费额度足),全程实战代码可直接复制,避开所有常见坑,让你半天就能落地一个可用的AI问答接口。

一、先搞懂:RAG到底是什么?

很多后端同学一听到RAG(Retrieval-Augmented Generation,检索增强生成)就头大,其实一句话就能说透:

RAG = 本地知识库检索 + 大模型生成,相当于给大模型配了一个“专属笔记本”。

举个例子:你想做一个公司内部的智能问答系统,用户问“公司差旅报销标准是什么”,如果直接调用智谱GLM,模型大概率答不上来(因为它没学过你公司的私有规则);但用RAG,系统会先从你本地的《差旅报销制度》里检索相关内容,再把检索到的内容传给GLM,让它基于这些真实资料生成答案——既不会胡说,又能精准匹配业务。对Java后端来说,RAG的核心价值就3个,精准戳中我们的需求:

  • 无需微调模型:不用花大量时间、算力训练模型,只需维护本地知识库,更新知识更简单。
  • 解决知识私有:企业内部文档、业务数据不用上传到第三方,兼顾安全与合规(Java后端最关心的点)。
  • 开发成本低:依托Spring生态和智谱GLM SDK,几行代码就能接入,不用切换技术栈。

二、核心流程:RAG+智谱GLM 完整链路

落地一个简单的「企业知识库问答系统」,核心流程分4步,每一步都对应Java代码实现:

  1. 准备工作:搭建环境、获取智谱GLM API密钥(免费);
  2. 知识库预处理:将本地文档(如PDF、TXT)转成向量,存入向量数据库;
  3. 检索环节:用户提问后,将问题转成向量,从向量库中检索最相关的知识片段;
  4. 生成环节:将检索到的知识+用户问题,传给智谱GLM,生成精准答案。
组件 选型 说明
后端框架 Spring Boot 3.x Java后端主流框架,快速搭建服务
向量数据库 PgVector(PostgreSQL扩展) 轻量、易集成,适合中小规模知识库(替代Milvus,降低部署成本)
Embedding模型 智谱GLM Embedding API 将文本转成向量,不用本地部署,直接调用接口
大模型 智谱GLM-4 国内性能优秀,免费额度足,Java SDK完善
文档处理 Apache Tika 解析PDF、TXT等文档,提取文本内容

三、实战环节:手把手实现RAG+智谱GLM适配

第一步:准备工作

1. 获取智谱GLM API密钥

1. 访问 智谱AI开放平台,注册账号并登录;
2. 进入「控制台」,创建应用,获取API密钥(sk);
3. 注意:新账号有免费额度,足够开发测试,后续按需付费即可。

2. 搭建项目环境(Maven依赖)

创建Spring Boot项目,在pom.xml中添加以下依赖:

<!-- Spring Boot 核心依赖 -->
<parent>
    <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
    <relativePath/>
</parent>

<dependencies>
    <!-- Spring Web(提供接口) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- 智谱GLM Java SDK(核心,适配GLM-4和Embedding) -->
    <dependency>
        <groupId>cn.bigmodel.openapi</groupId>
        <artifactId>openapi-java-sdk</artifactId>
        <version>release-v4-2.3.4</version>
    </dependency>
    
    <!-- PostgreSQL + PgVector(向量数据库) -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.github.pgvector</groupId>
        <artifactId>pgvector-spring-boot-starter</artifactId>
        <version>0.2.0</version>
    </dependency>
    
    <!-- 文档解析(Apache Tika) -->
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-core</artifactId>
        <version>2.9.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-parsers</artifactId>
        <version>2.9.1</version>
        <exclusions>
            <exclusion>
                <groupId>*</groupId>
                <artifactId>*</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <!-- Lombok(简化代码) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    <!-- 测试依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

3. 配置文件(application.yml)

替换自己的智谱API密钥、PostgreSQL地址,其他配置可默认:


spring:
  # 数据库配置(PostgreSQL,需提前创建数据库,并启用pgvector扩展)
  datasource:
    url: jdbc:postgresql://localhost:5432/rag-glm-db
    username: postgres
    password: 123456
    driver-class-name: org.postgresql.Driver
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true
    show-sql: true

# 智谱GLM配置
zhipu:
  glm:
    api-key: 你的智谱API密钥(sk)
    # GLM-4模型地址(默认无需修改)
    model: glm-4
    # Embedding模型地址(默认无需修改)
    embedding-model: embedding-3

# 服务器配置
server:
  port: 8080

第二步:初始化向量数据库(PgVector)

1. 先在PostgreSQL中创建数据库(如rag-glm-db);
2. 启用pgvector扩展(执行SQL):


-- 启用pgvector扩展
CREATE EXTENSION IF NOT EXISTS vector;
-- 创建知识库表(存储文本片段和对应的向量)
CREATE TABLE IF NOT EXISTS knowledge_chunk (
    id BIGSERIAL PRIMARY KEY,
    content TEXT NOT NULL, -- 文本片段
    embedding VECTOR(768) NOT NULL, -- 向量(智谱Embedding默认768维)
    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

3. 创建实体类(对应数据库表):


package com.example.ragglm.entity;

import io.github.pgvector.hibernate.VectorType;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Type;
import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "knowledge_chunk")
public class KnowledgeChunk {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 文本片段(知识库中的具体内容)
    @Column(columnDefinition = "TEXT NOT NULL")
    private String content;

    // 文本对应的向量(768维,智谱Embedding默认维度)
    @Column(name = "embedding", columnDefinition = "vector(768) NOT NULL")
    @Type(VectorType.class)
    private float[] embedding;

    // 创建时间
    @Column(name = "create_time", nullable = false, updatable = false)
    private LocalDateTime createTime = LocalDateTime.now();
}

4. 创建Repository(操作数据库):


package com.example.ragglm.repository;

import com.example.ragglm.entity.KnowledgeChunk;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import java.util.List;

public interface KnowledgeChunkRepository extends CrudRepository<KnowledgeChunk, Long> {

    // 核心检索方法:根据问题向量,查询Top3最相似的文本片段(余弦相似度)
    @Query(value = "SELECT * FROM knowledge_chunk ORDER BY embedding <-> :embedding LIMIT 3", nativeQuery = true)
    List<KnowledgeChunk> findTop3BySimilarity(@Param("embedding") float[] embedding);
}

第三步:封装智谱GLM工具类(核心,适配Embedding和问答)

这个工具类封装了两个核心方法:文本转向量(Embedding)、调用GLM生成答案:


package com.example.ragglm.util;

import cn.bigmodel.openapi.api.ChatApi;
import cn.bigmodel.openapi.api.EmbeddingApi;
import cn.bigmodel.openapi.core.ClientV4;
import cn.bigmodel.openapi.core.config.ClientConfig;
import cn.bigmodel.openapi.pojo.ChatCompletionRequest;
import cn.bigmodel.openapi.pojo.ChatCompletionResponse;
import cn.bigmodel.openapi.pojo.EmbeddingRequest;
import cn.bigmodel.openapi.pojo.EmbeddingResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class ZhipuGLMUtil {

    // 从配置文件中读取智谱API密钥
    @Value("${zhipu.glm.api-key}")
    private String apiKey;

    @Value("${zhipu.glm.model}")
    private String glmModel;

    @Value("${zhipu.glm.embedding-model}")
    private String embeddingModel;

    // 初始化智谱客户端(单例,避免重复创建)
    private ClientV4 getClient() {
        return new ClientV4.Builder(apiKey)
                .enableTokenCache() // 启用Token缓存,减少请求次数
                // 配置网络超时(避免调用超时)
                .networkConfig(30, 10, 10, 10, TimeUnit.SECONDS)
                .build();
    }

    /**
     * 文本转向量(Embedding):将文本片段/用户问题转成768维向量
     * @param text 输入文本(如“公司差旅报销标准”)
     * @return 向量数组(float[])
     */
    public float[] getEmbedding(String text) {
        try {
            ClientV4 client = getClient();
            EmbeddingApi embeddingApi = client.getEmbeddingApi();
            EmbeddingRequest request = new EmbeddingRequest();
            request.setModel(embeddingModel); // 智谱Embedding模型
            request.setInput(List.of(text)); // 输入文本(支持批量,这里单条)
            
            EmbeddingResponse response = embeddingApi.createEmbedding(request);
            // 返回第一个文本的向量(768维)
            return response.getData().get(0).getEmbedding();
        } catch (Exception e) {
            log.error("智谱Embedding接口调用失败:{}", e.getMessage(), e);
            throw new RuntimeException("文本转向量失败,请检查API密钥和网络");
        }
    }

    /**
     * 调用智谱GLM生成答案(结合检索到的知识片段)
     * @param userQuestion 用户问题
     * @param context 检索到的知识片段(多个用换行分隔)
     * @return 生成的答案
     */
    public String generateAnswer(String userQuestion, String context) {
        try {
            ClientV4 client = getClient();
            ChatApi chatApi = client.getChatApi();
            ChatCompletionRequest request = new ChatCompletionRequest();
            request.setModel(glmModel); // GLM-4模型
            request.setTemperature(0.7f); // 温度(0~1,越低越精准)
            
            // 构造Prompt:核心是让GLM基于context回答,不编造信息
            List<ChatCompletionRequest.Message> messages = new ArrayList<>();
            // 系统提示(固定,告诉模型回答规则)
            messages.add(new ChatCompletionRequest.Message(
                    "system",
                    "请严格根据提供的上下文内容回答用户问题,不要编造任何信息。如果上下文没有相关内容,直接回复“未找到相关信息”。"
            ));
            // 上下文(检索到的知识片段)
            messages.add(new ChatCompletionRequest.Message(
                    "user",
                    "上下文:" + context + "\n\n用户问题:" + userQuestion
            ));
            
            request.setMessages(messages);
            ChatCompletionResponse response = chatApi.createChatCompletion(request);
            // 返回生成的答案
            return response.getChoices().get(0).getMessage().getContent();
        } catch (Exception e) {
            log.error("智谱GLM接口调用失败:{}", e.getMessage(), e);
            throw new RuntimeException("生成答案失败,请检查API密钥和网络");
        }
    }
}

第四步:文档处理+知识库导入(本地文档转向量,存入数据库)

以PDF文档为例,实现“读取PDF内容→分割文本片段→转向量→存入数据库”,后续可扩展为接口上传文档:


package com.example.ragglm.service;

import com.example.ragglm.entity.KnowledgeChunk;
import com.example.ragglm.repository.KnowledgeChunkRepository;
import com.example.ragglm.util.ZhipuGLMUtil;
import lombok.RequiredArgsConstructor;
import org.apache.tika.Tika;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

@Service
@RequiredArgsConstructor
public class KnowledgeService {

    private final ZhipuGLMUtil zhipuGLMUtil;
    private final KnowledgeChunkRepository knowledgeChunkRepository;

    // 文档解析工具(Apache Tika)
    private final Tika tika = new Tika();

    /**
     * 导入PDF文档到知识库(核心方法)
     * @param pdfPath PDF文件路径(如:D:/company/差旅报销制度.pdf)
     */
    public void importPdfToKnowledgeBase(String pdfPath) {
        try {
            // 1. 读取PDF内容(提取文本)
            File file = new File(pdfPath);
            InputStream inputStream = new FileInputStream(file);
            String content = tika.parseToString(inputStream); // 解析PDF为文本
            
            // 2. 分割文本片段(避免文本过长,影响检索和生成)
            // 简单分割:按换行分割,也可使用语义分块(更精准)
            String[] chunks = content.split("\n");
            for (String chunk : chunks) {
                chunk = chunk.trim();
                // 过滤空文本和过短文本(避免无效数据)
                if (chunk.isEmpty() || chunk.length() < 10) {
                    continue;
                }
                
                // 3. 将文本片段转成向量
                float[] embedding = zhipuGLMUtil.getEmbedding(chunk);
                
                // 4. 存入数据库
                KnowledgeChunk knowledgeChunk = new KnowledgeChunk();
                knowledgeChunk.setContent(chunk);
                knowledgeChunk.setEmbedding(embedding);
                knowledgeChunkRepository.save(knowledgeChunk);
            }
            System.out.println("PDF文档导入知识库成功!");
        } catch (Exception e) {
            throw new RuntimeException("PDF导入失败:" + e.getMessage(), e);
        }
    }
}

第五步:实现RAG核心接口(用户提问→检索→生成答案)

编写一个接口,接收用户问题,完成“问题转向量→检索相似知识→调用GLM生成答案”的完整流程,可直接用于前端调用:


package com.example.ragglm.controller;

import com.example.ragglm.entity.KnowledgeChunk;
import com.example.ragglm.repository.KnowledgeChunkRepository;
import com.example.ragglm.service.KnowledgeService;
import com.example.ragglm.util.ZhipuGLMUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/rag")
@RequiredArgsConstructor
public class RagController {

    private final ZhipuGLMUtil zhipuGLMUtil;
    private final KnowledgeChunkRepository knowledgeChunkRepository;
    private final KnowledgeService knowledgeService;

    /**
     * 导入PDF到知识库(测试用,可后续改为文件上传接口)
     * @param pdfPath PDF文件路径
     * @return 导入结果
     */
    @GetMapping("/import-pdf")
    public String importPdf(@RequestParam String pdfPath) {
        try {
            knowledgeService.importPdfToKnowledgeBase(pdfPath);
            return "导入成功";
        } catch (Exception e) {
            return "导入失败:" + e.getMessage();
        }
    }

    /**
     * RAG核心接口:用户提问,返回精准答案
     * @param question 用户问题(如“公司差旅报销标准是什么”)
     * @return 生成的答案
     */
    @PostMapping("/query")
    public String ragQuery(@RequestBody String question) {
        // 1. 将用户问题转成向量
        float[] questionEmbedding = zhipuGLMUtil.getEmbedding(question);
        
        // 2. 检索Top3最相似的知识片段
        List<KnowledgeChunk> similarChunks = knowledgeChunkRepository.findTop3BySimilarity(questionEmbedding);
        
        // 3. 拼接检索到的知识片段(作为上下文)
        String context = similarChunks.stream()
                .map(KnowledgeChunk::getContent)
                .collect(Collectors.joining("\n"));
        
        // 4. 调用智谱GLM生成答案
        return zhipuGLMUtil.generateAnswer(question, context);
    }
}

第六步:测试运行

  1. 启动Spring Boot项目;
  2. 导入PDF文档:访问 http://localhost:8080/api/rag/import\-pdf?pdfPath=你的PDF路径,提示“导入成功”即完成;
  3. 测试问答接口:用Postman发送POST请求 http://localhost:8080/api/rag/query,请求体为用户问题(如“公司差旅报销标准是什么”);
  4. 查看响应:返回的答案会基于你导入的PDF内容,不会编造信息,且带有精准的业务逻辑。

四、避坑指南:落地RAG+GLM最容易踩的6个坑

很多同学按照上面的代码实现后,会遇到各种问题,这里总结了最常见的6个坑,以及对应的解决方案。

坑1:智谱API调用失败,提示“API密钥无效”

原因:API密钥错误、未启用应用,或密钥已过期。
解决方案:

  • 检查配置文件中的api-key,确保和智谱控制台的一致;
  • 登录智谱控制台,确认应用已启用,且免费额度未用完;
  • 如果是本地测试,不要用代理,避免网络拦截。

坑2:向量数据库检索不到相关内容,答案不准确

原因:文本分割不合理、Embedding模型不匹配,或检索相似度逻辑错误。
解决方案:

  • 文本分割:不要按固定长度分割,优先用“语义分块”(可引入LangChain4j简化),保留章节标题和上下文;
  • Embedding模型:必须用智谱的Embedding模型,不要混用其他模型(如OpenAI),否则向量维度不匹配;
  • 检索逻辑:PgVector余弦相似度,如果检索结果不理想,可调整返回的Top数量(如Top5)。

坑3:调用GLM接口超时,报“connection timeout”

原因:网络延迟、智谱API接口压力大,或超时时间设置过短。
解决方案:

  • 在智谱客户端配置中,延长超时时间;
  • 加入重试机制(如用Spring的Retry注解),避免单次请求失败;
  • 避免并发请求过多,可加入限流(如Redis限流)。

坑4:PDF解析乱码,提取的文本不完整

原因:PDF有加密、字体不兼容,或Tika依赖缺失。
解决方案:

  • 确保PDF未加密(加密PDF需先解密);
  • 补充Tika的字体依赖;
  • 如果是扫描版PDF(图片),需先进行OCR识别。

坑5:项目启动失败,提示“vector类型不存在”

原因:PgVector扩展未启用,或数据库版本过低(PostgreSQL需12+)。
解决方案:

  • 重新执行CREATE EXTENSION IF NOT EXISTS vector;,确保扩展启用;
  • 升级PostgreSQL到12及以上版本(推荐14+)。

坑6:生成的答案仍有编造,不符合上下文

原因:Prompt提示不明确,或GLM的temperature设置过高。
解决方案:

  • 优化Prompt:明确告诉模型“只能基于上下文回答,不能编造”;
  • 降低temperature值(设置为0.7,越低越精准,建议0.5~0.8之间);
  • 如果上下文无相关内容,让模型直接返回“未找到相关信息”,避免编造。

五、总结:Java后端落地RAG+GLM的核心要点

其实RAG并不复杂,对Java后端来说,核心就是“用Spring Boot搭框架、用智谱SDK做生成、用PgVector做检索”,全程不用脱离自己的技术栈,不用学复杂的AI理论。后续可扩展的方向:

  • 增加文件上传接口,支持前端上传文档;
  • 引入Redis缓存,缓存高频问题的检索结果,提升响应速度;
  • 优化文本分块逻辑,引入LangChain4j实现语义分块;
  • 增加权限控制,确保私有知识库的安全性。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐