Spring Boot + LangChain4j实现RAG——从零搭建企业级知识库问答系统

一、项目结构总览

bash 复制代码
rag-system/
├── pom.xml                          # 父POM,依赖管理
├── llm-common/                      # 公共模块
│   ├── pom.xml
│   └── src/main/java/com/peanutai/llm/common/
│       ├── exception/
│       │   ├── BusinessException.java
│       │   └── ErrorCode.java
│       └── response/
│           └── Result.java
│
├── llm-service/                     # 核心业务模块
│   ├── pom.xml
│   └── src/main/java/com/peanutai/llm/service/
│       ├── config/
│       │   └── LangChain4jConfig.java      # ChatModel/EmbeddingModel配置
│       ├── controller/
│       │   ├── ChatController.java         # 对话接口
│       │   ├── DocumentController.java     # 文档上传
│       │   ├── KnowledgeBaseController.java # 知识库管理
│       │   └── RagController.java          # RAG问答
│       ├── model/
│       │   ├── dto/                        # 请求/响应DTO
│       │   └── enums/                      # 枚举
│       ├── protection/
│       │   └── ContentSafetyService.java  # 内容安全
│       ├── rag/
│       │   ├── DocumentProcessor.java     # 文档解析+分块
│       │   └── PromptTemplate.java        # RAG Prompt模板
│       └── service/
│           ├── ChatService.java           # @AiService声明式接口
│           ├── EmbeddingService.java      # 向量化服务
│           ├── RagService.java            # RAG接口
│           ├── RagServiceImpl.java        # RAG核心实现
│           └── VectorStoreService.java    # 向量存储+检索
│
└── llm-gateway/                     # 网关模块
    ├── pom.xml
    └── src/main/java/com/peanutai/llm/gateway/
        └── GatewayApplication.java

二、Step 1:环境搭建(5分钟)

2.1 父POM依赖管理

xml 复制代码
<properties>
    <java.version>17</java.version>
    <spring-boot.version>3.2.0</spring-boot.version>
    <langchain4j.version>0.35.0</langchain4j.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>${langchain4j.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

关键 :用 langchain4j-bom 统一管理所有LangChain4j子模块版本,避免依赖冲突。

2.2 llm-service模块的POM

xml 复制代码
<dependencies>
    <!-- Spring Boot -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- LangChain4j 核心 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
    </dependency>
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-spring-boot-starter</artifactId>
    </dependency>

    <!-- 向量数据库 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-milvus</artifactId>
    </dependency>

    <!-- 文档解析 -->
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-core</artifactId>
        <version>2.9.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.tika</groupId>
        <artifactId>tika-parsers-standard-package</artifactId>
        <version>2.9.2</version>
    </dependency>

    <!-- 监控 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
</dependencies>

2.3 配置文件

yaml 复制代码
spring:
  application:
    name: llm-service

llm:
  openai:
    api-key: ${DASHSCOPE_API_KEY}
    base-url: https://dashscope.aliyuncs.com/compatible-mode/v1
    model: qwen-plus
  embedding:
    model: text-embedding-v3
    dimensions: 1024

milvus:
  host: ${MILVUS_HOST:localhost}
  port: ${MILVUS_PORT:19530}
  database: default

management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus

三、Step 2:配置LangChain4j(ChatModel + EmbeddingModel)

scss 复制代码
@Configuration
public class LangChain4jConfig {

    @Value("${llm.openai.api-key}")
    private String apiKey;

    @Value("${llm.openai.model:qwen-plus}")
    private String modelName;

    @Value("${llm.openai.base-url}")
    private String baseUrl;

    @Value("${llm.embedding.model:text-embedding-v3}")
    private String embeddingModelName;

    @Value("${llm.embedding.dimensions:1024}")
    private Integer embeddingDimensions;

    /**
     * 同步对话模型
     */
    @Bean
    public ChatModel chatModel() {
        return OpenAiChatModel.builder()
                .apiKey(apiKey)
                .modelName(modelName)
                .baseUrl(baseUrl)
                .timeout(Duration.ofSeconds(30))
                .temperature(0.7)
                .maxTokens(2000)
                .logRequests(true)
                .logResponses(true)
                .build();
    }

    /**
     * 流式对话模型
     */
    @Bean
    public StreamingChatModel streamingChatModel() {
        return OpenAiStreamingChatModel.builder()
                .apiKey(apiKey)
                .modelName(modelName)
                .baseUrl(baseUrl)
                .timeout(Duration.ofSeconds(60))
                .temperature(0.7)
                .maxTokens(2000)
                .build();
    }

    /**
     * Embedding模型
     */
    @Bean
    public EmbeddingModel embeddingModel() {
        return OpenAiEmbeddingModel.builder()
                .apiKey(apiKey)
                .modelName(embeddingModelName)
                .baseUrl(baseUrl)
                .dimensions(embeddingDimensions)
                .timeout(Duration.ofSeconds(30))
                .build();
    }
}

四、Step 3:文档解析与分块(DocumentProcessor)

这是RAG的第一步,决定后续检索质量。

java 复制代码
@Slf4j
@Component
public class DocumentProcessor {

    private static final int CHUNK_SIZE = 1000;    // 每块1000字符
    private static final int CHUNK_OVERLAP = 200;  // 重叠200字符
    private final Tika tika = new Tika();

    /**
     * 解析文档,提取纯文本
     * 支持:PDF、Word、Excel、PPT、HTML、Markdown等30+种格式
     */
    public String parse(MultipartFile file) throws IOException {
        try (InputStream is = file.getInputStream()) {
            String content = tika.parseToString(is);
            if (content == null || content.isBlank()) {
                throw new IOException("文档内容为空,无法解析: " + file.getOriginalFilename());
            }
            return content.strip();
        } catch (TikaException e) {
            throw new IOException("文档解析失败: " + file.getOriginalFilename(), e);
        }
    }

    /**
     * 文本分块:固定长度 + 滑动窗口重叠
     * 重叠200字符确保语义不被切断
     */
    public List<TextSegment> chunk(String content) {
        List<TextSegment> chunks = new ArrayList<>();
        int length = content.length();

        for (int start = 0; start < length; start += (CHUNK_SIZE - CHUNK_OVERLAP)) {
            int end = Math.min(start + CHUNK_SIZE, length);
            String chunk = content.substring(start, end).strip();
            if (!chunk.isBlank()) {
                chunks.add(TextSegment.from(chunk));
            }
        }
        log.info("文档分块完成: 共{}个块", chunks.size());
        return chunks;
    }
}

分块参数调优经验

场景 chunkSize overlap 说明
通用文档 1000 200 适合大多数场景
问答FAQ 500 100 短文本,精度优先
技术文档 1500 300 代码块需要完整
法律合同 2000 400 条款需要完整上下文

五、Step 4:向量化服务(EmbeddingService)

less 复制代码
@Slf4j
@Service
@RequiredArgsConstructor
public class EmbeddingService {

    private final EmbeddingModel embeddingModel;
    private static final int BATCH_SIZE = 10;

    /**
     * 单条向量化(用于用户问题)
     */
    public Embedding embed(String text) {
        return embeddingModel.embed(text).content();
    }

    /**
     * 批量向量化(用于文档入库)
     * DashScope单次最多10条,需分批
     */
    public List<Embedding> embedAll(List<String> texts) {
        List<TextSegment> segments = texts.stream()
                .map(TextSegment::from)
                .collect(Collectors.toList());

        List<Embedding> allEmbeddings = new ArrayList<>();
        for (int i = 0; i < segments.size(); i += BATCH_SIZE) {
            List<TextSegment> batch = segments.subList(
                i, Math.min(i + BATCH_SIZE, segments.size()));
            List<Embedding> batchResult = embeddingModel.embedAll(batch).content();
            allEmbeddings.addAll(batchResult);
        }
        return allEmbeddings;
    }
}

六、Step 5:向量存储与检索(VectorStoreService)

这是RAG最核心的环节,包含了多知识库隔离和相似度检索。

scss 复制代码
@Slf4j
@Service
public class VectorStoreService {

    private static final String KB_ID_FIELD = "knowledgeBaseId";
    private final EmbeddingStore<TextSegment> embeddingStore;

    public VectorStoreService(EmbeddingStore<TextSegment> embeddingStore) {
        this.embeddingStore = embeddingStore;
    }

    /**
     * 批量写入向量
     * 为每个文档块写入 knowledgeBaseId 元数据,实现多知识库隔离
     */
    public void addAll(String knowledgeBaseId, 
                       List<TextSegment> chunks, 
                       List<Embedding> embeddings) {
        if (chunks.size() != embeddings.size()) {
            throw new IllegalArgumentException("chunks和embeddings数量不一致");
        }
        for (int i = 0; i < chunks.size(); i++) {
            TextSegment segment = chunks.get(i);
            segment.metadata().put(KB_ID_FIELD, knowledgeBaseId);
            embeddingStore.add(embeddings.get(i), segment);
        }
        log.info("向量存储完成: kbId={}, count={}", knowledgeBaseId, chunks.size());
    }

    /**
     * 向量相似度检索
     * 通过 knowledgeBaseId 过滤,确保只搜指定知识库
     * minScore=0.6 过滤低相似度结果
     */
    public List<DocumentMatch> search(String knowledgeBaseId, 
                                       Embedding queryEmbedding, 
                                       int maxResults) {
        Filter kbFilter = new IsEqualTo(KB_ID_FIELD, knowledgeBaseId);

        return embeddingStore.search(
                EmbeddingSearchRequest.builder()
                        .queryEmbedding(queryEmbedding)
                        .maxResults(maxResults)
                        .minScore(0.6)
                        .filter(kbFilter)
                        .build())
                .matches().stream()
                .map(m -> DocumentMatch.builder()
                        .content(m.embedded().text())
                        .score(m.score())
                        .build())
                .collect(Collectors.toList());
    }

    /**
     * 按知识库删除
     */
    public void deleteCollection(String knowledgeBaseId) {
        Filter kbFilter = new IsEqualTo(KB_ID_FIELD, knowledgeBaseId);
        embeddingStore.removeAll(kbFilter);
        log.info("向量集合已按知识库删除: kbId={}", knowledgeBaseId);
    }
}

多知识库隔离原理

arduino 复制代码
Milvus向量库
│
├── knowledgeBaseId: "kb-hr"
│   ├── chunk1: "年假政策:入职满1年..."
│   ├── chunk2: "请假流程:OA系统..."
│   └── chunk3: "考勤制度:上班时间..."
│
├── knowledgeBaseId: "kb-tech"
│   ├── chunk1: "Spring Boot配置..."
│   ├── chunk2: "数据库连接池..."
│   └── chunk3: "RESTful API设计..."
│
└── knowledgeBaseId: "kb-product"
    ├── chunk1: "产品功能说明..."
    └── chunk2: "定价策略..."

查询时通过 new IsEqualTo("knowledgeBaseId", "kb-hr") 过滤,只返回人事制度相关内容。


七、Step 6:Prompt模板(PromptTemplate)

typescript 复制代码
@Component
public class PromptTemplate {

    private static final String RAG_SYSTEM_PROMPT = """
            你是一个智能知识库助手,请根据以下参考资料回答用户问题。
            
            规则:
            1. 仅基于参考资料回答,不要编造信息
            2. 如果参考资料不足以回答问题,请明确说明
            3. 回答要简洁、准确、有条理
            4. 使用中文回答
            
            参考资料:
            %s
            """;

    public String build(String question, String context) {
        return String.format(RAG_SYSTEM_PROMPT, context) + "\n\n用户问题:" + question;
    }
}

八、Step 7:RAG核心服务(RagServiceImpl)

这是整个系统的核心,串联了所有环节。

java 复制代码
@Slf4j
@Service
public class RagServiceImpl implements RagService {

    private final DocumentProcessor documentProcessor;
    private final EmbeddingService embeddingService;
    private final VectorStoreService vectorStoreService;
    private final ChatModel chatModel;
    private final StreamingChatModel streamingChatModel;
    private final PromptTemplate promptTemplate;
    private final ContentSafetyService contentSafetyService;

    public RagServiceImpl(DocumentProcessor documentProcessor,
                          EmbeddingService embeddingService,
                          VectorStoreService vectorStoreService,
                          ChatModel chatModel,
                          StreamingChatModel streamingChatModel,
                          PromptTemplate promptTemplate,
                          ContentSafetyService contentSafetyService) {
        this.documentProcessor = documentProcessor;
        this.embeddingService = embeddingService;
        this.vectorStoreService = vectorStoreService;
        this.chatModel = chatModel;
        this.streamingChatModel = streamingChatModel;
        this.promptTemplate = promptTemplate;
        this.contentSafetyService = contentSafetyService;
    }

    // ==================== 文档入库 ====================

    @Override
    public void ingestDocument(MultipartFile file, String knowledgeBaseId) {
        log.info("开始处理文档入库: file={}, kbId={}", 
                 file.getOriginalFilename(), knowledgeBaseId);

        try {
            // ① 解析文档
            String content = documentProcessor.parse(file);
            // ② 文本分块
            List<TextSegment> chunks = documentProcessor.chunk(content);
            // ③ 批量向量化
            List<Embedding> embeddings = embeddingService.embedAll(
                    chunks.stream().map(TextSegment::text).collect(Collectors.toList()));
            // ④ 写入向量库
            vectorStoreService.addAll(knowledgeBaseId, chunks, embeddings);

            log.info("文档入库完成: file={}", file.getOriginalFilename());
        } catch (IOException e) {
            log.error("文档解析失败", e);
            throw new BusinessException(ErrorCode.DOCUMENT_PARSE_ERROR);
        }
    }

    // ==================== RAG查询 ====================

    @Override
    public RagResponse query(String question, String knowledgeBaseId) {
        long startTime = System.currentTimeMillis();

        // [1] 输入安全校验
        contentSafetyService.validateInput(question);

        // [2] 问题向量化
        Embedding questionEmbedding = embeddingService.embed(question);

        // [3] 向量检索Top-5
        List<DocumentMatch> matches = vectorStoreService.search(
                knowledgeBaseId, questionEmbedding, 5);

        // [4] 拼接上下文
        String context = matches.stream()
                .map(DocumentMatch::getContent)
                .collect(Collectors.joining("\n\n"));

        // [5] 构造Prompt
        String prompt = promptTemplate.build(question, context);

        // [6] 调用大模型
        ChatRequest chatRequest = ChatRequest.builder()
                .messages(UserMessage.from(prompt))
                .build();
        ChatResponse response = chatModel.chat(chatRequest);

        // [7] 输出审核
        String answer = response.aiMessage().text();
        contentSafetyService.auditOutput(answer);

        // [8] 构造响应
        long latency = System.currentTimeMillis() - startTime;
        TokenUsage tokenUsage = response.tokenUsage();

        return RagResponse.builder()
                .answer(answer)
                .sources(convertToSources(matches))
                .tokenUsage(com.peanutai.llm.service.model.dto.TokenUsage.builder()
                        .inputTokens(tokenUsage.inputTokenCount())
                        .outputTokens(tokenUsage.outputTokenCount())
                        .totalTokens(tokenUsage.totalTokenCount())
                        .build())
                .latencyMs(latency)
                .model("qwen-plus")
                .build();
    }

    // ==================== 流式RAG查询 ====================

    @Override
    public SseEmitter queryStream(String question, String knowledgeBaseId) {
        SseEmitter emitter = new SseEmitter(30000L);

        CompletableFuture.runAsync(() -> {
            try {
                // 同样的RAG流程,但用流式模型
                contentSafetyService.validateInput(question);
                Embedding questionEmbedding = embeddingService.embed(question);
                List<DocumentMatch> matches = vectorStoreService.search(
                        knowledgeBaseId, questionEmbedding, 5);

                String context = matches.stream()
                        .map(DocumentMatch::getContent)
                        .collect(Collectors.joining("\n\n"));

                String prompt = promptTemplate.build(question, context);

                streamingChatModel.chat(prompt, new StreamingChatResponseHandler() {
                    @Override
                    public void onPartialResponse(String partialResponse) {
                        try {
                            emitter.send(SseEmitter.event()
                                    .name("message")
                                    .data(partialResponse));
                        } catch (IOException e) {
                            log.error("SSE发送失败", e);
                        }
                    }

                    @Override
                    public void onCompleteResponse(ChatResponse completeResponse) {
                        try {
                            // 发送来源文档
                            emitter.send(SseEmitter.event()
                                    .name("sources")
                                    .data(convertToSources(matches)));
                            emitter.complete();
                        } catch (IOException e) {
                            log.error("SSE完成发送失败", e);
                        }
                    }

                    @Override
                    public void onError(Throwable error) {
                        log.error("流式查询失败", error);
                        emitter.completeWithError(error);
                    }
                });
            } catch (Exception e) {
                log.error("流式查询异常", e);
                emitter.completeWithError(e);
            }
        });

        return emitter;
    }
}

九、Step 8:Controller层

9.1 文档上传

less 复制代码
@RestController
@RequestMapping("/api/v1/documents")
@RequiredArgsConstructor
public class DocumentController {

    private final RagService ragService;

    @PostMapping("/upload")
    public Result<Void> upload(
            @RequestParam("file") MultipartFile file,
            @RequestParam("knowledgeBaseId") String knowledgeBaseId) {
        ragService.ingestDocument(file, knowledgeBaseId);
        return Result.success();
    }

    @PostMapping("/batch-upload")
    public Result<Void> batchUpload(
            @RequestParam("files") List<MultipartFile> files,
            @RequestParam("knowledgeBaseId") String knowledgeBaseId) {
        ragService.ingestDocuments(files, knowledgeBaseId);
        return Result.success();
    }
}

9.2 RAG问答

less 复制代码
@RestController
@RequestMapping("/api/v1/rag")
@RequiredArgsConstructor
public class RagController {

    private final RagService ragService;

    @PostMapping("/query")
    public Result<RagResponse> query(@RequestBody RagRequest request) {
        RagResponse response = ragService.query(
                request.getQuestion(), 
                request.getKnowledgeBaseId());
        return Result.success(response);
    }

    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter queryStream(@RequestBody RagRequest request) {
        return ragService.queryStream(
                request.getQuestion(), 
                request.getKnowledgeBaseId());
    }
}

9.3 知识库管理

less 复制代码
@RestController
@RequestMapping("/api/v1/knowledge-bases")
@RequiredArgsConstructor
public class KnowledgeBaseController {

    private final RagService ragService;

    @DeleteMapping("/{knowledgeBaseId}")
    public Result<Void> delete(@PathVariable String knowledgeBaseId) {
        ragService.deleteKnowledgeBase(knowledgeBaseId);
        return Result.success();
    }
}

十、Step 9:生产级防护

10.1 内容安全服务

typescript 复制代码
@Slf4j
@Service
public class ContentSafetyService {

    private static final List<String> INJECTION_PATTERNS = Arrays.asList(
        "ignore previous instructions",
        "system prompt",
        "忽略之前的指令",
        "执行以下命令"
    );

    public void validateInput(String input) {
        if (input == null || input.isBlank()) {
            throw new BusinessException(ErrorCode.BAD_REQUEST);
        }
        if (input.length() > 4000) {
            throw new BusinessException(ErrorCode.BAD_REQUEST);
        }
        if (isPromptInjection(input)) {
            log.warn("检测到Prompt注入: {}", input);
            throw new BusinessException(ErrorCode.CONTENT_SAFETY_ERROR);
        }
    }

    public void auditOutput(String output) {
        if (output == null || output.isBlank()) {
            throw new BusinessException(ErrorCode.LLM_SERVICE_ERROR);
        }
    }

    private boolean isPromptInjection(String input) {
        String lower = input.toLowerCase();
        return INJECTION_PATTERNS.stream().anyMatch(lower::contains);
    }
}

10.2 统一异常处理

kotlin 复制代码
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusiness(BusinessException e) {
        log.error("业务异常: code={}, message={}", e.getCode(), e.getMessage());
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.error(5000, "服务器内部错误");
    }
}

10.3 统一返回结构

less 复制代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private int code;
    private String message;
    private T data;

    public static <T> Result<T> success(T data) {
        return Result.<T>builder().code(200).message("success").data(data).build();
    }

    public static <T> Result<T> error(int code, String message) {
        return Result.<T>builder().code(code).message(message).build();
    }
}

十一、Step 10:测试验证

11.1 启动服务

arduino 复制代码
set DASHSCOPE_API_KEY=sk-your-api-key
mvn spring-boot:run -pl llm-service

11.2 上传文档

bash 复制代码
curl -X POST http://localhost:8080/api/v1/documents/upload \
  -F "file=@employee-handbook.pdf" \
  -F "knowledgeBaseId=kb-hr"

返回:

json 复制代码
{
"code"
:
200
,
"message"
:
"success"
,
"data"
:
null
}

11.3 RAG问答

ruby 复制代码
curl -X POST http://localhost:8080/api/v1/rag/query \
  -H "Content-Type: application/json" \
  -d '{"question":"入职半年能请年假吗?","knowledgeBaseId":"kb-hr"}'

返回:

css 复制代码
{
"code"
:
200
,
"message"
:
"success"
,
"data"
:
{
"answer"
:
"根据公司员工年假政策,入职满1年才享有年假。您目前入职半年,暂不满足条件。"
,
"sources"
:
[
{
"fileName"
:
"employee-handbook.pdf"
,
"content"
:
"入职满1年享有5天年假..."
,
"similarityScore"
:
0.89
}
]
,
"tokenUsage"
:
{
"inputTokens"
:
520
,
"outputTokens"
:
45
,
"totalTokens"
:
565
}
,
"latencyMs"
:
850
,
"model"
:
"qwen-plus"
}
}

11.4 流式问答

ruby 复制代码
curl -N -X POST http://localhost:8080/api/v1/rag/stream \
  -H "Content-Type: application/json" \
  -d '{"question":"年假怎么申请?","knowledgeBaseId":"kb-hr"}'

返回(逐字输出):

vbnet 复制代码
event:message
data:根据公司

event:message
data:《员工手册》

event:message
data:第3.2条

...

event:sources
data:[{"fileName":"employee-handbook.pdf",...}]

十二、RAG效果调优

12.1 参数调优指南

参数 默认值 调优方向 效果
chunkSize 1000 减小→精度↑ 增大→上下文↑ 500-2000
chunkOverlap 200 增大→语义连贯↑ token↑ 100-400
maxResults(Top-K) 5 增大→覆盖↑ 噪声↑ 3-10
minScore 0.6 增大→精度↑ 召回↓ 0.5-0.8
temperature 0.7 减小→确定↑ 创意↓ 0.3-1.0

12.2 常见问题与解决

问题 原因 解决方案
检索不到相关文档 minScore太高 降到0.5
检索到无关文档 Top-K太大 减到3,加minScore
回答不准确 上下文太短 增大chunkSize
回答断章取义 分块切断了语义 增大overlap
延迟太高 检索+生成慢 加缓存、降Top-K
Token消耗高 上下文太长 减chunkSize、减Top-K

十三、总结

13.1 完整链路回顾

scss 复制代码
文档上传 → Tika解析 → 文本分块(1000+200) → 批量向量化(1024维)
    → 写入Milvus(带knowledgeBaseId元数据) → 入库完成

用户提问 → 输入安全校验 → 问题向量化 → 向量检索(Top-5, minScore≥0.6)
    → 拼接上下文 → 构造Prompt → 大模型生成 → 输出审核 → 返回答案

13.2 关键数字

指标 数值
代码行数 ~500行(核心逻辑)
文档格式支持 30+(Tika)
向量维度 1024(通义千问v3)
分块策略 1000字符 + 200重叠
检索Top-K 5
相似度阈值 0.6
平均延迟 800-1200ms
平均Token 500-600/次

13.3 下一步

这个RAG系统已经可以投入生产使用。下一篇文章将介绍RAG的进阶优化:查询改写、重排序、缓存策略,进一步提升检索效果。

相关推荐
H_Peak1 小时前
Python运算符与流程控制:if条件判断
后端
H_Peak1 小时前
Python字符串操作:从基础到正则入门
后端
自进化Agent智能体1 小时前
Hermes 子代理委派 —— 并行工作的艺术
后端
步行cgn1 小时前
MyBatis 动态 SQL 中的 <foreach> 标签完全解析
后端
SomeB1oody2 小时前
【RustyML入门】5.0. 模型评估
开发语言·后端·机器学习·rust·教程
唐青枫3 小时前
别只把 enum 当常量列表:Zig 枚举、状态机与 Tagged Union 实战
后端
2601_963870213 小时前
【计算机毕业设计】基于Spring Boot的非遗文创产品交易平台的设计与实现
java·spring boot·后端
lv__pf3 小时前
Spring之AOP底层源码解析(下)【TL spring14】
java·后端·spring