RAG知识库

zy123
2025-08-19 /  0 评论 /  0 点赞 /  8 阅读 /  2776 字
最近更新于 08-25

Spring AI + Ollama

1. Ollama

  • 定位:本地/容器化的 大模型推理服务引擎
  • 作用:负责加载模型、执行推理、返回结果
  • 支持的模型:Qwen、DeepSeek、nomic-embed-text(embedding 模型)等
  • 交互方式:REST API(默认 http://localhost:11434
ollama run qwen2.5:7b-instruct

2. Spring AI

  • 定位:Spring 官方推出的 AI 接入框架
  • 作用:统一封装对各种 AI 服务的调用逻辑,让开发者更容易接入(OpenAI、Ollama 等)
  • 关键点
    • OllamaChatModel:封装了调用 Ollama 的对话接口
    • OllamaOptions:指定模型、参数(温度、上下文大小等)
    • PgVectorStore:对接向量数据库(Spring AI 提供的默认向量数据库适配器。)
    • TokenTextSplitter:默认的文本切分器 (Spring AI 内置逻辑)

3.pgVectorStore

pgVectorStoreSpring AI 提供的向量存储接口实现,底层基于 PostgreSQL + pgvector 插件。它在项目中的作用:

存储向量和元数据

pgVectorStore.accept(splits);

这行代码会:

  • splits 中每个 Documenttext → 调用 embedding 模型 → 生成向量
  • Document.metadata → 存入 PostgreSQL 的 vector_store 表(以 JSONB 格式存储)
  • 将生成的向量(embedding) 存入 vector_store 表中的 embedding
  • Document.text(原始文本内容) 存入 vector_store 表中的 content

检索时自动生成 SQL

List<Document> documents = pgVectorStore.similaritySearch(request);

在执行检索时,pgVectorStore 会自动生成 SQL 查询,结合 向量相似度filterExpression(如 metadata->>'knowledge' = 'xxx')进行查询,返回最相关的文档片段。

Docker如何开启GPU加速大模型推理

巨坑!

默认是CPU跑大模型,deepseek1.5b勉强能跑动,但速度很慢,一换qwen2.5:7b模型瞬间就跑不动了,这才发现一直在用CPU跑!!

如何排查?

1.查看ollama日志:

load_backend: loaded CPU backend from /usr/lib/ollama/libggml-cpu-alderlake.so

2.本地cmd命令行输入,查看显存占用率。

nvidia-smi

|   0  NVIDIA GeForce RTX 4060 ...  WDDM  |   00000000:01:00.0  On |                  N/A |
| N/A   58C    P0             24W /  110W |    2261MiB /   8188MiB |      6%      Default |

设备状态

  • RTX 4060显卡驱动(576.02)和CUDA 12.9环境正常
  • 当前GPU利用率仅6%GPU-Util列)
  • 显存占用2261MB/8188MB(约27.6%)

可见模型推理的时候压根没有用GPU!!!

解决

参考博客:1-3 Windows Docker Desktop安装与设置docker实现容器GPU加速_windows docker gpu-CSDN博客

1)配置WSL2,打开 PowerShell(以管理员身份运行),执行以下命令:

wsl --install -d Ubuntu  # 安装 Linux 发行版
wsl --set-default-version 2  # 设为默认版本
wsl --update  # 更新内核
  • 重启计算机以使更改生效。

2)检查wsl是否安装成功

C:\Users\zhangsan>wsl --list --verbose
  NAME              STATE           VERSION
* Ubuntu            Running         2
  docker-desktop    Running         2

这个命令会列出所有已安装的Linux发行版及其状态。如果看到列出的Linux发行版,说明WSL已成功安装。

3)安装docker desktop默认配置:

image-20250819211426320

4)保险起见也启用一下开启 Windows 的 Hyper-V 虚拟化技术:

搜索“启用或关闭Windows功能”,勾选:Hyper-V 虚拟化技术。

image-20250819211715265

5) 命令提示符输入nvidia-smi

C:\Users\zhangsan>nvidia-smi
Tue Aug 19 21:18:11 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 576.02                 Driver Version: 576.02         CUDA Version: 12.9     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                  Driver-Model | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4060 ...  WDDM  |   00000000:01:00.0  On |                  N/A |
| N/A   55C    P4             12W /  129W |    2132MiB /   8188MiB |     33%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

若显示GPU信息(如型号、显存等),则表示支持。

6)Docker Desktop配置**(重要)**

  • 打开 Docker 设置 → Resources → WSL Integration → 启用 Ubuntu 实例。

  • 进入 Docker 设置 → Docker Engine,添加以下配置:

    • {
        "experimental": true,
        "features": {
          "buildkit": true
         },
        "registry-mirrors": ["https://registry.docker-cn.com"]  // 可选镜像加速
      }
      
  • 保存并重启 Docker。

7)验证 GPU 加速

docker run --rm -it --gpus=all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark

NOTE: The CUDA Samples are not meant for performance measurements. Results may vary when GPU Boost is enabled.

> Windowed mode
> Simulation data stored in video memory
> Single precision floating point simulation
> 1 Devices used for simulation
MapSMtoCores for SM 8.9 is undefined.  Default to use 128 Cores/SM
MapSMtoArchName for SM 8.9 is undefined.  Default to use Ampere
GPU Device 0: "Ampere" with compute capability 8.9

> Compute 8.9 CUDA device: [NVIDIA GeForce RTX 4060 Laptop GPU]
24576 bodies, total time for 10 iterations: 17.351 ms
= 348.102 billion interactions per second
= 6962.039 single-precision GFLOP/s at 20 flops per interaction

成功输出应包含 GPU 型号及性能指标 [NVIDIA GeForce RTX 4060 Laptop GPU]

8)Ollama验证:

image-20250819220345141

在容器中也能显示GPU了,说明配置成功了!!!

version: '3.9'
services:
  ollama:
    image: registry.cn-hangzhou.aliyuncs.com/xfg-studio/ollama:0.5.10
    container_name: ollama
    restart: unless-stopped
    ports:
      - "11434:11434"
    volumes:
      - ./ollama:/root/.ollama
    runtime: nvidia
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
      - NVIDIA_DRIVER_CAPABILITIES=all

RAG(检索增强生成)

postgre向量数据库

表结构:

PgAdmin软件下:ai-rag-knowledge->架构->public->表->vector_store

image-20250820170837656

id → 每条数据的唯一标识(主键/uuid)。

content → 存放 chunk 的原始文本。

metadata → 存放 JSON 格式的额外信息(文件名、路径、知识库标签等)。

embedding → 存放向量,类型是 vector(N)(N 由 Embedding 模型决定,比如 768)。

所有文件切分出来的 chunk 都存在这一张表里,每条记录就是一个 chunk。

作用

相似度检索:用 embedding <-> embedding

结果展示:取出 content

溯源/过滤:用 metadata

查询

image-20250820171104405

image-20250820171145997

还有 metadataembedding 列显示不下。

A. 索引/入库(Ingestion)

  1. 文档读取器

    • TikaDocumentReader 把上传的 PDF、Word、TXT、PPT 等文件解析成 List<Document>
    • 每个 Document 包含 textmetadata(比如 pagesource)。
    • 相当于是“把二进制文件 → 转成纯文本段落”。
  2. 清洗过滤

    • 剔除空文本,避免无效数据进入后续流程。
  3. 文档切分器

    • TokenTextSplitter 把每个 Document 再切成 chunk(默认 800 tokens 一段,可配置)。
    • 目的是避免超长文本超过 Embedding 模型的输入限制。
    @Bean
    public TokenTextSplitter tokenTextSplitter() {
        return TokenTextSplitter.builder()
                .withChunkSize(600)          // 每段最多 600 token
                .withMinChunkSizeChars(300)  // 每段至少 300 字符
                .withMinChunkLengthToEmbed(10)
                .withMaxNumChunks(10000)
                .withKeepSeparator(true)
                .build();
    }
    
    

    withMinChunkSizeChars(300): 如果某个切分块的文本少于 300 个字符,TokenTextSplitter 会避免直接拆分它,而是尝试 合并它与下一个切分块,直到符合字符数要求。

  4. 打元数据

    • 给原始文档和 chunk 都加上 knowledge(ragTag)、pathoriginal_filename 等信息。
    • 方便后续检索时追溯来源。
  5. Embedding 模型

    • 每个 chunk 调用 Ollama 的 nomic-embed-text,生成一个固定维度的向量(如 768 维)。
    • ⚠️ 注意:这是对 chunk 整体嵌入,不是对单个 token。
  6. 向量存储

    • pgvector 存储 [embedding 向量 + metadata + 原始文本]
    • 后续可以通过向量相似度检索,结合 metadata 实现溯源。
    • ⚠️ 向量维度Embedding 模型决定,pgvector 表的维度必须保持一致(如 768/1024/1536)。
/**
 * RAG 知识库构建:读取文件、拆分、打标签、存储到向量库
 */
private void processAndStoreFile(
        org.springframework.core.io.Resource resource,
        String ragTag,
        String normalizedPath,
        String originalFilename) {

    try {
        // 1. 读取文件内容
        TikaDocumentReader documentReader = new TikaDocumentReader(resource);
        List<Document> documents = documentReader.get();

        // 2. 过滤空文档
        List<Document> docs = new ArrayList<>(documents);
        docs.removeIf(d -> d.getText() == null || d.getText().trim().isEmpty());
        if (docs.isEmpty()) {
            log.warn("文件内容为空,跳过处理: {}", normalizedPath);
            return;
        }

        // 3. 文本切分(默认 800 tokens/块)
        List<Document> splits = tokenTextSplitter.apply(docs);

        // 4. 设置元数据(原始文档 + 拆分文档)
        docs.forEach(doc -> {
            doc.getMetadata().put("knowledge", ragTag);
            doc.getMetadata().put("path", normalizedPath);
            doc.getMetadata().put("original_filename", originalFilename);
        });
        splits.forEach(doc -> {
            doc.getMetadata().put("knowledge", ragTag);
            doc.getMetadata().put("path", normalizedPath);
            doc.getMetadata().put("original_filename", originalFilename);
        });

        // 5. 存储到向量数据库(只需要写入拆分后的块)
        pgVectorStore.accept(splits);
        log.info("文件处理完成: {}", normalizedPath);

    } catch (Exception e) {
        log.error("文件处理失败:{} - {}", normalizedPath, e.getMessage(), e);
    }
}

B. 检索/回答(Query)

接收用户问题

  • 输入用户的问题文本 message
  • 使用同一个 Embedding 模型(如 nomic-embed-text)将问题转为查询向量。

相似度检索

  • 在向量库中用 余弦相似度 / 内积 搜索相似 chunk。
  • 可附加条件过滤:如 knowledge == 'xxx',只在某个知识库范围内查。
  • 常用参数:
    • topK:取最相似的前 K 个结果(例如 5~8)。
    • minSimilarityScore(可选):过滤低相关度结果。
// 1) 相似度检索(带 ragTag 过滤)
        SearchRequest request = SearchRequest.builder()
                .query(message)
                .topK(8)
                .filterExpression("knowledge == '" + ragTag + "'")
                .build();
        List<Document> documents = pgVectorStore.similaritySearch(request);

⚡️ 注意:这里的 knowledge 是存储在 metadata JSONB 里的字段,pgVectorStore 会自动翻译成 SQL(如 metadata->>'knowledge' = 'xxx')。

拼装文档上下文

  • 把检索到的文档片段拼接成系统提示中的 DOCUMENTS 部分。
  • 可以在拼接时附带 metadata(如文件名、页码),方便溯源。
String documentContent = documents.stream()
        .map(doc -> "[来源:" + doc.getMetadata().get("original_filename") + "]\n" + doc.getText())
        .collect(Collectors.joining("\n\n---\n\n"));

构造提示词(Prompt)

  • 使用 SystemPromptTemplate 注入 DOCUMENTS 内容。
  • System Prompt 应该放在 用户消息之前,确保模型优先遵循规则。
Message ragMessage = new SystemPromptTemplate(SYSTEM_PROMPT)
        .createMessage(Map.of("documents", documentContent));

List<Message> messages = new ArrayList<>();
messages.add(ragMessage);  // 先放系统提示
messages.add(new UserMessage(message));

调用对话模型(流式返回)

return ollamaChatModel.stream(new Prompt(
        messages,
        OllamaOptions.builder().model(model).build()
));

优化

1.优化分词逻辑

image-20250820175639642

这块比较复杂,要切的恰到好处...切的不大不小。

2.更新向量嵌入模型

MCP服务

简介 - 模型上下文协议

1)引入POM

<dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>

<dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-mcp-client-webflux-spring-boot-starter</artifactId>
</dependency>

2)配置MCP

resources/config/mcp-servers-config.json

{
  "mcpServers": {
    "filesystem": {
      "command": "npx.cmd",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "D:/folder/MCP-test",
        "D:/folder/MCP-test"
      ]
    },
    "mcp-server-computer": {
      "command": "java",
      "args": [
        "-Dspring.ai.mcp.server.stdio=true",
        "-jar",
        "D:/folder/study/apache-maven-3.8.4/mvn_repo/edu/whut/mcp/mcp-server-computer/1.0.0/mcp-server-computer-1.0.0.jar"
      ]
    }
  }
}

application.yml:

image-20250819144924047

这实际上告诉系统用 npx 去启动一个 MCP Filesystem Server,路径指向你的 Desktop

需要提前下载该文件服务,https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem

1)先装 Node.js,配置环境变量

2)安装服务

npm install -g @modelcontextprotocol/server-filesystem

3)配置客户端

如果有多套 AI 对话模型,包括Ollama(deepseek、qwen)、OpenAI(gpt-4o),需要指定使用调用哪个接口。

@Bean
public ChatClient.Builder chatClientBuilder(OllamaChatModel ollamaChatModel) {
    return new DefaultChatClientBuilder(
            ollamaChatModel,
            ObservationRegistry.NOOP,
            (ChatClientObservationConvention) null
    );
}

本项目调用的ollamaChatModel。注意,它是一个 Ollama 服务的客户端,真正的模型选择(deepseek、qwen、mistral…)是通过调用时传入的 OllamaOptions 来指定的。eg:

ChatResponse response = chatClientBuilder
        .defaultOptions(OllamaOptions.builder().model("qwen2.5:7b-instruct").build())
        .build()
        .prompt("你好,介绍一下你自己")
        .call();

4)测试

大模型会自动调用所能使用的工具!!!

@GetMapping("/test-workflow")
    public String testWorkflow(@RequestParam String question) {
        var chatClient = chatClientBuilder
                .defaultOptions(OllamaOptions.builder().model("qwen2.5:7b-instruct").build())
                .build();

        ChatResponse response = chatClient
                .prompt(question)
                .tools(tools)
                .call()
                .chatResponse();
        return response.toString();
    }

@GetMapping("/tools")
    public Object listTools() {
        return Arrays.stream(tools.getToolCallbacks())
                .map(cb -> Map.of(
                        "name", cb.getName(),
                        "description", cb.getDescription()
                ))
                .toList();
    }

image-20250820101236265

image-20250820101525612

有时候大模型不会去调用Tools,可能是模型能力不够。

© 版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 收藏
评论 抢沙发
取消