## 核心改动
在上一版本基础上,补充完整的 Milvus (L1) 向量索引功能。
### 新增依赖注入
```java
@Autowired
private DocumentChunkService documentChunkService;
@Autowired
private VectorIndexService vectorIndexService;
@Autowired
private VectorEmbeddingService vectorEmbeddingService;
```
### 完整的数据流
```
knowledge_base/*.md
↓ 1. 扫描 & 解析 frontmatter
↓ 2. 保存到 MySQL (api_document)
↓ 3. 提取正文 & 文档分块
↓ 4. 生成向量并索引到 Milvus
↓ 5. 加入 L0 内存索引
完成 (L0 + L1 双层索引)
```
### 关键代码
```java
// 1. 提取正文(去除 frontmatter)
String body = extractBody(content);
// 2. 文档分块
List<DocumentChunk> chunks = documentChunkService.chunkDocument(body, relativePath);
// 3. 上传到 Milvus
vectorIndexService.indexDocumentChunks(document.getDocId(), chunks, category);
// 4. 更新状态
document.setStatus("INDEXED");
document.setChunkCount(chunks.size());
```
### 错误处理
- Milvus 索引失败时:
- 更新文档状态为 FAILED
- 记录错误信息到 error_message 字段
- 继续处理下一个文档(不中断整个流程)
### 响应示例
```json
{
"success": true,
"scanned": 6,
"inserted": 6,
"failed": 0,
"details": {
"api/payment-errors.md": "导入成功(L0+L1)"
}
}
```
### 数据库字段
新增:
- `chunk_count`:分块数量
- `error_message`:错误信息(失败时)
## 验证步骤
```bash
# 1. 启动应用(确保 Milvus 已运行)
mvn spring-boot:run
# 2. 初始化知识库
curl -X POST http://localhost:9900/api/knowledge/init
# 3. 验证结果
# - MySQL: 检查 api_document 表
# - Milvus: 检查 knowledge_base_collection
# - L0: 日志显示"知识库索引加载完成,共 6 个文档"
# 4. 测试 L1 语义检索
# lookup_knowledge("支付为什么会失败")
# 应该返回 semantic_L1 结果
```
## 文档更新
- 更新使用文档,删除"暂未实现 L1"的说明
- 添加 Milvus 数据结构说明
- 添加 Milvus 相关错误处理
470 lines
9.8 KiB
Markdown
470 lines
9.8 KiB
Markdown
# 知识库初始化 API 使用文档
|
||
|
||
## 概述
|
||
|
||
提供了知识库批量初始化接口,用于将 `knowledge_base` 目录下的所有 Markdown 文档导入到数据库和向量索引(L0 + L1)。
|
||
|
||
**功能特点**:
|
||
1. ✅ **批量扫描**:递归扫描 knowledge_base 目录下所有 .md 文件
|
||
2. ✅ **自动去重**:基于文件路径检查,避免重复导入
|
||
3. ✅ **数据入库**:保存文档元数据到 MySQL
|
||
4. ✅ **L0 索引**:自动加入内存精确匹配索引
|
||
5. ✅ **L1 索引**:文档分块并上传到 Milvus 向量数据库
|
||
|
||
---
|
||
|
||
## API 接口
|
||
|
||
### 1. 初始化知识库
|
||
|
||
**端点**:
|
||
```
|
||
POST /api/knowledge/init?force=false
|
||
```
|
||
|
||
**参数**:
|
||
- `force`(可选):是否强制重新导入,跳过去重检查
|
||
- `false`(默认):跳过已存在的文档
|
||
- `true`:强制重新导入所有文档
|
||
|
||
**请求示例**:
|
||
```bash
|
||
# 首次导入(去重模式)
|
||
curl -X POST http://localhost:9900/api/knowledge/init
|
||
|
||
# 强制重新导入
|
||
curl -X POST http://localhost:9900/api/knowledge/init?force=true
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "知识库初始化完成",
|
||
"scanned": 6,
|
||
"skipped": 0,
|
||
"inserted": 6,
|
||
"failed": 0,
|
||
"details": {
|
||
"api/payment-errors.md": "导入成功(L0+L1)",
|
||
"domain/spring-ai-tool-best-practices.md": "导入成功(L0+L1)",
|
||
"infrastructure/flyway-best-practices.md": "导入成功(L0+L1)",
|
||
"infrastructure/mysql-connection-pool.md": "导入成功(L0+L1)",
|
||
"infrastructure/redis-config.md": "导入成功(L0+L1)",
|
||
"troubleshooting/fault-diagnosis-process.md": "导入成功(L0+L1)"
|
||
}
|
||
}
|
||
```
|
||
|
||
**字段说明**:
|
||
- `scanned`:扫描到的文件总数
|
||
- `skipped`:跳过的文件数量(已存在)
|
||
- `inserted`:成功导入的文件数量
|
||
- `failed`:失败的文件数量
|
||
- `details`:每个文件的处理结果详情
|
||
|
||
---
|
||
|
||
### 2. 查询知识库统计
|
||
|
||
**端点**:
|
||
```
|
||
GET /api/knowledge/stats
|
||
```
|
||
|
||
**请求示例**:
|
||
```bash
|
||
curl http://localhost:9900/api/knowledge/stats
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"totalDocuments": 6,
|
||
"totalVectors": 48,
|
||
"categories": {
|
||
"api": 1,
|
||
"domain": 1,
|
||
"infrastructure": 3,
|
||
"troubleshooting": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
**字段说明**:
|
||
- `totalDocuments`:数据库中的文档总数
|
||
- `totalVectors`:Milvus 中的向量总数(chunk 数量)
|
||
- `categories`:按分类统计的文档数量
|
||
|
||
---
|
||
|
||
## 使用场景
|
||
|
||
### 场景 1:项目启动时初始化
|
||
|
||
```bash
|
||
# 1. 启动应用
|
||
mvn spring-boot:run
|
||
|
||
# 2. 等待应用启动完成(约 10 秒)
|
||
|
||
# 3. 调用初始化接口
|
||
curl -X POST http://localhost:9900/api/knowledge/init
|
||
|
||
# 4. 查看结果
|
||
# 日志输出:知识库初始化完成: 扫描=6, 跳过=0, 新增=6, 失败=0
|
||
```
|
||
|
||
---
|
||
|
||
### 场景 2:添加新文档后重新初始化
|
||
|
||
```bash
|
||
# 1. 添加新文档到 knowledge_base 目录
|
||
echo "---
|
||
title: 新文档
|
||
keywords: [测试, test]
|
||
summary: 这是一个测试文档
|
||
category: test
|
||
---
|
||
|
||
# 新文档内容
|
||
" > knowledge_base/test/new-doc.md
|
||
|
||
# 2. 调用初始化接口(去重模式)
|
||
curl -X POST http://localhost:9900/api/knowledge/init
|
||
|
||
# 3. 查看结果
|
||
# 只会导入新文档,跳过已存在的 6 个文档
|
||
# 响应: scanned=7, skipped=6, inserted=1, failed=0
|
||
```
|
||
|
||
---
|
||
|
||
### 场景 3:强制重新导入所有文档
|
||
|
||
```bash
|
||
# 适用场景:
|
||
# - 数据库被清空,需要重新导入
|
||
# - 文档内容有更新,需要刷新
|
||
# - 索引损坏,需要重建
|
||
|
||
curl -X POST http://localhost:9900/api/knowledge/init?force=true
|
||
|
||
# 响应: scanned=6, skipped=0, inserted=6, failed=0
|
||
```
|
||
|
||
---
|
||
|
||
## 去重机制
|
||
|
||
### 去重依据
|
||
- **文件路径**:相对于 `knowledge_base` 目录的相对路径
|
||
- 示例:`api/payment-errors.md`
|
||
|
||
### 去重逻辑
|
||
```
|
||
if (!force && existingFilePaths.contains(relativePath)) {
|
||
跳过该文档
|
||
} else {
|
||
导入该文档
|
||
}
|
||
```
|
||
|
||
### 注意事项
|
||
1. **文件移动会被视为新文档**:
|
||
```bash
|
||
# 移动前:api/payment-errors.md
|
||
# 移动后:errors/payment-errors.md
|
||
# 结果:会被当作两个不同的文档
|
||
```
|
||
|
||
2. **文件重命名会被视为新文档**:
|
||
```bash
|
||
# 重命名前:payment-errors.md
|
||
# 重命名后:payment-error-codes.md
|
||
# 结果:会被当作两个不同的文档
|
||
```
|
||
|
||
3. **内容更新不触发重新导入**(非 force 模式):
|
||
```bash
|
||
# 修改文件内容后调用 init(非 force)
|
||
# 结果:跳过该文档,数据库中仍是旧内容
|
||
# 解决:使用 force=true 强制重新导入
|
||
```
|
||
|
||
---
|
||
|
||
## 数据存储
|
||
|
||
### 完整的数据流
|
||
|
||
```
|
||
knowledge_base/*.md
|
||
↓ 1. 扫描
|
||
KnowledgeBaseInitService
|
||
↓ 2. 解析 frontmatter
|
||
Frontmatter (title, keywords, summary)
|
||
↓ 3. 保存到数据库
|
||
MySQL (api_document)
|
||
↓ 4. 提取正文 & 分块
|
||
DocumentChunkService
|
||
↓ 5. 生成向量
|
||
VectorEmbeddingService
|
||
↓ 6. 索引到 Milvus
|
||
Milvus (L1 向量索引)
|
||
↓ 7. 加入内存索引
|
||
KnowledgeIndexService (L0)
|
||
```
|
||
|
||
---
|
||
|
||
### 数据库表结构(api_document)
|
||
|
||
| 字段 | 类型 | 说明 | 示例 |
|
||
|------|------|------|------|
|
||
| `id` | BIGINT | 主键 | 1 |
|
||
| `doc_id` | VARCHAR(64) | 文档唯一标识 | uuid |
|
||
| `file_name` | VARCHAR(256) | 文件名 | payment-errors.md |
|
||
| `file_path` | VARCHAR(512) | 相对路径 | api/payment-errors.md |
|
||
| `api_name` | VARCHAR(128) | 文档标题 | 支付网关错误码定义 |
|
||
| `status` | VARCHAR(16) | 状态 | INDEXED / FAILED |
|
||
| `chunk_count` | INT | 分块数量 | 8 |
|
||
| `error_message` | TEXT | 错误信息 | null |
|
||
| `metadata` | TEXT | Frontmatter JSON | {"title":"...","keywords":[...]} |
|
||
| `file_size` | BIGINT | 文件大小(字节) | 2048 |
|
||
| `indexed_at` | DATETIME | 索引时间 | 2026-06-25 10:00:00 |
|
||
|
||
### metadata JSON 结构
|
||
|
||
```json
|
||
{
|
||
"title": "支付网关错误码定义",
|
||
"summary": "记录了支付网关所有核心错误码的含义及排查方向",
|
||
"category": "api",
|
||
"keywords": ["ERR_TIMEOUT","超时","支付网关"]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Milvus 向量索引
|
||
|
||
每个文档会被分块(chunk)并生成向量,存储到 Milvus 集合中:
|
||
|
||
**Collection**: `knowledge_base_collection`
|
||
|
||
**字段**:
|
||
- `doc_id`:文档 ID
|
||
- `chunk_id`:分块 ID
|
||
- `chunk_text`:分块文本内容
|
||
- `embedding`:768 维向量
|
||
- `category`:文档分类
|
||
- `file_path`:文件路径
|
||
|
||
**分块策略**:
|
||
- Chunk Size:根据 `DocumentChunkConfig` 配置(默认 500 token)
|
||
- Overlap:重叠区域(默认 50 token)
|
||
|
||
---
|
||
|
||
## L0 内存索引
|
||
|
||
导入过程会自动将文档加入 `KnowledgeIndexService` 的内存索引:
|
||
|
||
```java
|
||
KnowledgeEntry entry = KnowledgeEntry.builder()
|
||
.filePath(relativePath)
|
||
.title(title)
|
||
.keywords(keywords)
|
||
.summary(summary)
|
||
.category(category)
|
||
.build();
|
||
knowledgeIndexService.addToIndex(entry);
|
||
```
|
||
|
||
**验证 L0 索引**:
|
||
```bash
|
||
# 应用启动后查看日志
|
||
grep "知识库索引加载完成" logs/application.log
|
||
|
||
# 输出示例:
|
||
# [INFO] 知识库索引加载完成,共 6 个文档
|
||
```
|
||
|
||
---
|
||
|
||
## 错误处理
|
||
|
||
### 常见错误
|
||
|
||
#### 1. 目录不存在
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "初始化失败: 知识库目录不存在: knowledge_base"
|
||
}
|
||
```
|
||
|
||
**解决**:
|
||
```bash
|
||
mkdir -p knowledge_base/api
|
||
mkdir -p knowledge_base/infrastructure
|
||
mkdir -p knowledge_base/domain
|
||
mkdir -p knowledge_base/troubleshooting
|
||
```
|
||
|
||
---
|
||
|
||
#### 2. 文档格式无效
|
||
```json
|
||
{
|
||
"success": true,
|
||
"scanned": 6,
|
||
"inserted": 5,
|
||
"failed": 1,
|
||
"details": {
|
||
"test/invalid.md": "格式无效: frontmatter 解析失败"
|
||
}
|
||
}
|
||
```
|
||
|
||
**原因**:
|
||
- 缺少 frontmatter
|
||
- YAML 格式错误
|
||
- 缺少必填字段(title, keywords, summary)
|
||
|
||
**解决**:
|
||
```markdown
|
||
---
|
||
title: 文档标题
|
||
keywords: [关键词1, 关键词2]
|
||
summary: 文档摘要
|
||
category: api
|
||
---
|
||
|
||
# 正文内容
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 4: Milvus 连接失败
|
||
|
||
**症状**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"scanned": 6,
|
||
"inserted": 0,
|
||
"failed": 6,
|
||
"details": {
|
||
"api/payment-errors.md": "Milvus 索引失败: Connection refused"
|
||
}
|
||
}
|
||
```
|
||
|
||
**原因**:
|
||
- Milvus 服务未启动
|
||
- 网络连接问题
|
||
- 配置错误
|
||
|
||
**解决**:
|
||
```bash
|
||
# 检查 Milvus 是否运行
|
||
docker ps | grep milvus
|
||
|
||
# 检查配置
|
||
grep milvus application.yml
|
||
|
||
# 启动 Milvus
|
||
docker-compose up -d milvus-standalone
|
||
```
|
||
|
||
---
|
||
|
||
### 问题 5: 文档分块失败
|
||
|
||
**症状**:
|
||
```json
|
||
{
|
||
"details": {
|
||
"test/large-doc.md": "Milvus 索引失败: Document too large"
|
||
}
|
||
}
|
||
```
|
||
|
||
**原因**:
|
||
- 文档内容过大
|
||
- 分块配置不当
|
||
|
||
**解决**:
|
||
- 检查 `DocumentChunkConfig` 配置
|
||
- 调整 chunk size 和 overlap
|
||
|
||
---
|
||
|
||
#### 3. 文档缺少标题
|
||
```json
|
||
{
|
||
"details": {
|
||
"test/no-title.md": "缺少标题"
|
||
}
|
||
}
|
||
```
|
||
|
||
**解决**:在 frontmatter 中添加 `title` 字段。
|
||
|
||
---
|
||
|
||
## 最佳实践
|
||
|
||
### ✅ 推荐做法
|
||
|
||
1. **首次启动后立即初始化**:
|
||
```bash
|
||
mvn spring-boot:run
|
||
sleep 15 # 等待启动完成
|
||
curl -X POST http://localhost:9900/api/knowledge/init
|
||
```
|
||
|
||
2. **新增文档后增量导入**:
|
||
```bash
|
||
# 不使用 force,只导入新文档
|
||
curl -X POST http://localhost:9900/api/knowledge/init
|
||
```
|
||
|
||
3. **定期检查统计信息**:
|
||
```bash
|
||
curl http://localhost:9900/api/knowledge/stats
|
||
```
|
||
|
||
4. **更新文档内容后强制刷新**:
|
||
```bash
|
||
curl -X POST http://localhost:9900/api/knowledge/init?force=true
|
||
```
|
||
|
||
---
|
||
|
||
### ❌ 避免做法
|
||
|
||
1. **不检查响应就认为成功**:
|
||
- 始终检查 `failed` 字段
|
||
- 查看 `details` 了解具体失败原因
|
||
|
||
2. **频繁使用 force=true**:
|
||
- 会重复插入数据(违反唯一约束)
|
||
- 建议先清理数据库,再使用 force
|
||
|
||
3. **不检查文档格式就导入**:
|
||
- 先手动验证 frontmatter 格式
|
||
- 确保必填字段完整
|
||
|
||
---
|
||
|
||
## 相关文档
|
||
|
||
- **知识库使用指南**:`mvp/architecture/knowledge-retrieval-usage.md`
|
||
- **知识库架构**:`mvp/architecture/knowledge-retrieval-architecture.md`
|
||
- **Executor Prompt**:`src/main/resources/prompts/executor-prompt.md`
|