前面的 RAG 程序只能从命令行运行。每次提问都要执行 Maven 命令,浏览器、前端或其他服务也无法直接复用它。
这一阶段使用 Spring Boot 把 RAG 流程变成一个长期运行的 HTTP 服务:调用方只需要提交问题,服务负责检索知识、调用模型并返回答案和来源。
浏览器或其他应用
↓ HTTP POST /api/rag/ask
Spring Boot Controller
↓
RagService
├── bge-m3 → Qdrant 检索
└── 检索结果 → Prompt → qwen3:14b
↓
答案和真实来源 JSON
这样做的重点不是换一种启动方式,而是学习一个 AI 功能如何成为可复用、可测试、可配置的后端接口。
05-spring-rag 是独立模块,不会修改 04-qdrant-rag。第 4 个模块继续保留命令行 RAG,第 5 个模块在查询 API 的基础上继续加入动态入库和浏览器工作台。本篇只聚焦最先完成的查询 API;后续能力分别在第 9、10 篇讲解。
完成后,你会:
- 启动一个 Spring Boot RAG 服务。
- 用 JSON 调用
POST /api/rag/ask。 - 理解 Controller、Service 和外部客户端的分工。
- 理解
answer和sources是怎样生成的。 - 用配置文件调整模型、Collection、Top-K 和相似度阈值。
- 识别参数错误、知识不足和外部服务故障这三类结果。
本模块依赖 Ollama、bge-m3、qwen3:14b 和 Qdrant。先检查 Ollama:
ollama list
curl -s http://localhost:11434/api/tags | jq模型列表中应包含:
bge-m3
qwen3:14b
再检查 Qdrant:
docker ps --filter name=qdrant-study
curl -s http://localhost:6333/collections/kubernetes_chunks | jq '{
status: .result.status,
points_count: .result.points_count
}'预期 Collection 状态为 green,并且 points_count 大于 0。
如果 Qdrant 容器已经创建但没有运行:
docker start qdrant-study如果 Collection 还没有资料,先按照第 7 篇完成创建和入库:
mvn -f 04-qdrant-rag/pom.xml compile exec:java \
-Dexec.args=--index05-spring-rag
├── pom.xml
└── src
├── main
│ ├── java/com/example/ai/rag
│ │ ├── SpringRagApplication.java
│ │ ├── api/ HTTP 接口、请求响应和异常处理
│ │ ├── service/ RAG 流程和核心业务对象
│ │ ├── client/ Ollama 与 Qdrant 的调用实现
│ │ ├── config/ Spring Bean 和类型安全配置
│ │ └── ingestion/ Markdown 切分、Point 映射和知识入库
│ └── resources
│ ├── application.yml
│ └── static/ HTML、CSS 和 JavaScript 工作台
└── test 各层自动化测试
主要类的职责:
| 类 | 职责 |
|---|---|
RagController |
接收 HTTP 请求并校验 question |
RagService |
编排检索、Prompt、生成答案和来源列表 |
QdrantKnowledgeRetriever |
将问题向量化并搜索 Qdrant |
RagPromptBuilder |
把命中的 Chunk 和问题组装成 Prompt |
OllamaAnswerGenerator |
调用 qwen3:14b 生成答案 |
RagConfiguration |
创建并连接上述对象 |
RagProperties |
读取并校验 application.yml 配置 |
一次正常请求经过以下步骤:
RagController从 JSON 中取得question。- Bean Validation 检查问题非空且不超过 2000 个字符。
RagService调用KnowledgeRetriever。OllamaEmbeddingClient使用bge-m3把问题变成 1024 维向量。QdrantKnowledgeRetriever用问题向量执行 Top-K 搜索和阈值过滤。RagPromptBuilder把命中的 Chunk 与原始问题组装成 Prompt。OllamaAnswerGenerator调用qwen3:14b。RagService用同一批检索结果生成sources,与答案一起返回。
这里有两个重要边界:
- Qdrant 没有返回达到阈值的 Chunk 时,不调用聊天模型,直接回答“根据现有资料无法确定”。
sources不是大模型编写的,而是 Java 根据 Qdrant 的真实结果生成的。
配置文件是 05-spring-rag/src/main/resources/application.yml:
server:
port: 8080
rag:
ollama:
base-url: http://localhost:11434
embedding-model: bge-m3
chat-model: qwen3:14b
connect-timeout: 5s
request-timeout: 2m
qdrant:
host: localhost
port: 6334
use-tls: false
collection: kubernetes_chunks
request-timeout: 10s
search:
top-k: 2
minimum-score: 0.60关键配置含义:
server.port:Spring Boot 对外监听端口。embedding-model:把问题转换成向量的模型。chat-model:根据知识资料生成答案的大模型。qdrant.port:Java SDK 使用的 gRPC 端口6334。collection:要搜索的 Qdrant Collection。top-k:最多取回几个知识片段。minimum-score:允许进入 Prompt 的最低相似度。
在仓库根目录运行:
mvn -f 05-spring-rag/pom.xml spring-boot:run看到类似下面的日志表示启动成功:
Started SpringRagApplication
服务地址是:
http://localhost:8080
直接用浏览器打开这个地址会进入 RAG 知识工作台。工作台和本篇的查询接口属于同一个 Spring Boot 应用;如果只想验证 API,可以继续使用下一步的 curl 命令。
另开一个终端执行:
curl -sS -X POST http://localhost:8080/api/rag/ask \
-H 'Content-Type: application/json' \
-d '{"question":"密码、令牌和证书应该保存在哪里?"}' | jq示例响应:
{
"answer": "密码、令牌和证书应该保存在 Kubernetes 的 Secret 中。",
"sources": [
{
"pointId": "4",
"source": "kubernetes.md",
"title": "Secret",
"score": 0.6504381895065308
}
]
}字段含义:
| 字段 | 来源 |
|---|---|
answer |
qwen3:14b 根据 Prompt 生成 |
sources |
Java 根据 Qdrant 的真实检索结果生成 |
pointId |
Qdrant Point ID,API 统一使用字符串以兼容数字 ID 和 UUID |
source |
入库时保存在 payload 中的文件名 |
title |
入库时保存在 payload 中的 Chunk 标题 |
score |
Qdrant 返回的向量相似度 |
curl -sS -X POST http://localhost:8080/api/rag/ask \
-H 'Content-Type: application/json' \
-d '{"question":"Ingress 应该怎样配置 TLS 证书?"}' | jq如果没有 Chunk 达到 0.60,返回:
{
"answer": "根据现有资料无法确定。",
"sources": []
}HTTP 状态仍然是 200,因为系统正常完成了检索,只是知识库没有足够资料。这不是服务器错误。
提交空问题:
curl -sS -X POST http://localhost:8080/api/rag/ask \
-H 'Content-Type: application/json' \
-d '{"question":""}' | jq返回 HTTP 400:
{
"code": "VALIDATION_ERROR",
"message": "question 不能为空"
}问题超过 2000 个字符时也返回 400,并且不会调用 Ollama 或 Qdrant。
如果 Ollama 或 Qdrant 无法访问,接口返回 HTTP 503:
{
"code": "EXTERNAL_SERVICE_UNAVAILABLE",
"message": "外部服务暂时不可用"
}三种结果不要混淆:
| 情况 | HTTP 状态 | 含义 |
|---|---|---|
| 正常回答 | 200 |
找到资料并生成答案 |
| 没有相关资料 | 200 |
服务正常,但知识不足 |
| 请求参数错误 | 400 |
调用方需要修改问题 |
| Ollama 或 Qdrant 故障 | 503 |
外部依赖暂时不可用 |
mvn -f 05-spring-rag/pom.xml test测试覆盖:
- RAG 编排、无资料短路和来源生成。
- 请求 JSON、参数校验与错误响应。
- Ollama 请求格式和响应解析。
- Qdrant 搜索参数和 payload 映射。
- Spring 配置绑定和完整 Bean 创建。
- 根地址能够通过真实 HTTP 请求返回工作台 HTML。
客户端测试会启动临时本地 HTTP 服务模拟 Ollama,不要求真正调用模型。这样测试执行快,并且结果稳定。真实服务仍需使用 curl 再验证一次。
临时换一个端口:
mvn -f 05-spring-rag/pom.xml spring-boot:run \
-Dspring-boot.run.arguments=--server.port=8081调用地址也要改成 http://localhost:8081。
依次检查:
curl -s http://localhost:11434/api/tags | jq
docker ps --filter name=qdrant-study
curl -s http://localhost:6333/collections/kubernetes_chunks | jq检查 Collection 是否已经入库,并观察 points_count。如果有数据,再检查 minimum-score 是否过高,以及查询内容是否确实在示例知识库中。
停止并重新启动 Spring Boot。开发模式下,配置不会因为保存文件而自动重载。
在运行 Spring Boot 的终端按:
Control + C
这只停止 Java API。Ollama 和 Qdrant 是独立服务,不会一起停止。
- Spring Boot 能在
http://localhost:8080启动。 - 知识库内问题返回模型答案和非空
sources。 - 知识库外问题返回固定答案和空
sources。 - 空问题返回 HTTP
400。 mvn -f 05-spring-rag/pom.xml test全部通过。
下一阶段为 Spring Boot 增加动态知识入库能力,让 Markdown 在切分、向量化并写入 Qdrant 后立即可以查询。继续阅读第 9 篇(上):知识库动态入库核心流程。