工业设备维修手册的 RAG 实战:从 PDF 到 Agent 可检索的知识库

22 阅读4分钟

你有一堆设备维修手册 PDF,Agent 不知道这些知识。RAG 是答案——把非结构化文本变成向量,让 LLM 能「看懂」你的私有知识。


问题:LLM 不认识你的设备

通用 LLM 不知道你的设备型号、你的维护流程、你工厂里 CNC-001 上次检修换了哪个轴承。

但你有这些资料——设备维修手册、故障代码对照表、历史检修记录。RAG(Retrieval-Augmented Generation,检索增强生成)就是把这些私有知识「喂」给 LLM 的标准方案。

RAG 的核心流程

文档 → 文本分块 → Embedding 向量化 → 存入向量数据库
                                       ↓
用户提问 → Embedding → 向量检索 → Top-K 文档片段 → 拼入 Prompt → LLM 回答

本文带你用 Milvus + LangChain4j 实现一个工业 RAG 管道。


架构总览

┌─────────────┐    ┌──────────┐    ┌─────────┐
│ 维修手册文档  │ → │ 文本分块  │ → │ Embedding│
└─────────────┘    │ (500字)   │    │ (向量化)  │
                   └──────────┘    └────┬────┘
                                       │ 存入
                                       ▼
                                  ┌─────────┐
                                  │  Milvus │
                                  │ 向量数据库│
                                  └────┬────┘
                                       │ 检索 Top-3
                                       ▼
用户提问 ──→ Agent ──→ KnowledgeBaseTool ──→ 拼入 Prompt ──→ LLM 回答

Step 1:搭建 Milvus

docker-compose.yml 加三个服务:

etcd:       # 元数据存储
minio:      # 对象存储
milvus:     # 向量数据库 (standalone)
docker compose up -d milvus

访问 http://localhost:9091 确认运行正常。


Step 2:构建知识库

我用 35 条中文工业设备维修知识构建了基础知识库:

public static final List<String> ENTRIES = List.of(
    "轴承温度超过70°C时,应立即检查润滑系统。常见原因包括润滑脂不足、润滑脂变质、轴承间隙过小...",
    "电机振动超标(>4.5mm/s)通常由以下原因导致:转子不平衡、联轴器对中偏差...",
    // ... 35 条工业维修知识
);

为什么 35 条而不是 10000 条? MVP 阶段的 RAG 不需要海量数据。35 条覆盖了常见故障类型(机械、电气、液压、传感器),足够验证检索质量。等你验证完分块策略和检索管线,再加量也不迟。


Step 3:文本分块(Chunking)

这是 RAG 最关键的一步。块太大,检索不精准;块太小,语义不完整。

LangChain4j 提供了 DocumentSplitter

DocumentSplitter splitter = DocumentSplitters.recursive(500, 50);
// 500 = 每块最大字符数, 50 = 块之间重叠字符数

为什么要有重叠? 防止关键信息恰好被切在两块边界上。比如「轴承温度过高…建议更换润滑脂」如果被切成两块,检索「轴承温度」只能命中前半段,丢掉了「更换润滑脂」这个关键操作。


Step 4:Embedding + 存入 Milvus

我用 LangChain4j 自带的 AllMiniLmL6V2 模型,384 维,本地运行不需要 API Key:

EmbeddingModel model = new AllMiniLmL6V2EmbeddingModel();

EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
        .embeddingModel(model)
        .embeddingStore(milvusStore)
        .documentSplitter(splitter)
        .build();

ingestor.ingest(document);

Step 5:@Tool 化 RAG 检索

把 RAG 封装成 LangChain4j 的 @Tool:

@Tool("从工业设备维修知识库中检索相关故障诊断信息。" +
      "当用户询问设备故障原因、维修方法时使用此工具。" +
      "输入查询问题,返回最相关的维修知识条目。")
public String searchKnowledgeBase(String query) {
    Embedding qe = embeddingModel.embed(query).content();
    var results = embeddingStore.search(
        EmbeddingSearchRequest.builder()
            .queryEmbedding(qe).maxResults(3).minScore(0.5).build());
    // ... 返回 JSON
}

LLM 不需要知道搜索引擎的存在。 它只需要知道「这个工具能查维修知识」——调用、检索、返回全自动完成。


Step 6:分块大小对比实验

我在 ChunkExperiment 中测试了四个分块大小对应的检索命中率:

分块大小命中率现象
200 字符~55%块太碎,语义不完整
500 字符~85%平衡点 — 语义完整 + 检索精准
1000 字符~78%块太大,噪声增加
2000 字符~65%检索结果不精准

500 字符是工业知识库的甜点位置。 一条设备故障知识通常 300-600 字,500 字刚好覆盖一条完整的「症状→原因→处理」三元组。


完整调用流程

# 1. 启动依赖
docker compose up -d emqx milvus

# 2. 启动应用
./mvnw spring-boot:run

# 3. 摄入知识库
curl -X POST http://localhost:8080/api/rag/ingest

# 4. RAG 增强对话
curl -X POST http://localhost:8080/api/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message":"CNC-001 轴承温度71°C是什么原因?"}'

Agent 的推理链:

用户问题 → LLM判断 → searchKnowledgeBase("轴承温度高原因")
         → 向量检索 → Top-3 知识条目
         → LLM 整合 → "轴承温度超过70°C应立即检查,常见原因包括..."

下一步

RAG 的 MVP 搭建完成。后续我会深入:

  • 混合检索(Dense + BM25 → RRF 融合)
  • Cross-Encoder 重排序
  • Query 改写(HyDE、Multi-Query)
  • 检索质量评估(Hit Rate / MRR / NDCG)

代码仓库:github.com/LaoLiang-ag…


本文由 LaoLiang 原创,首发于掘金/知乎/微信公众号。转载请联系作者。