fix(knowledge): 将 category 存储到 fault_source 字段
## 问题
数据库表 api_document 没有独立的 category 字段,导致 frontmatter 的 category 信息无法正确存储。
### 表结构分析
```sql
CREATE TABLE api_document (
fault_category VARCHAR(32) DEFAULT 'EXTERNAL_API', -- 固定枚举,不合适存储自定义分类
fault_source VARCHAR(128), -- 可以存储自定义分类
...
)
```
## 解决方案
使用 `fault_source` 字段存储 frontmatter 的 category:
```java
// 保存时
document.setFaultSource(category); // api, infrastructure, domain, troubleshooting
// 统计时
Map<String, Long> categoryCount = apiDocumentRepository.findAll().stream()
.collect(Collectors.groupingBy(
doc -> doc.getFaultSource() != null ? doc.getFaultSource() : "general",
Collectors.counting()
));
```
## 字段映射关系
| Frontmatter | 数据库字段 | 示例值 |
|-------------|-----------|--------|
| `title` | `api_name` | "支付网关错误码定义" |
| `category` | `fault_source` | "api" / "infrastructure" |
| `keywords` | `metadata` (JSON) | ["ERR_TIMEOUT","超时"] |
| `summary` | `metadata` (JSON) | "记录了..." |
## 优势
1. **充分利用现有字段**:fault_source (VARCHAR 128) 足够存储分类
2. **避免枚举限制**:不受 FaultCategory 枚举约束
3. **查询方便**:直接通过 fault_source 字段查询和统计
4. **向后兼容**:metadata 中仍保留完整的 frontmatter 信息
## 验证
```bash
# 初始化
curl -X POST http://localhost:9900/api/knowledge/init
# 查询统计
curl http://localhost:9900/api/knowledge/stats
# 响应
{
"categories": {
"api": 1,
"infrastructure": 3,
"domain": 1,
"troubleshooting": 1
}
}
```
## 数据库查询
```sql
-- 按分类统计
SELECT fault_source, COUNT(*)
FROM api_document
GROUP BY fault_source;
-- 结果
api | 1
infrastructure | 3
domain | 1
troubleshooting | 1
```
This commit is contained in:
@@ -19,6 +19,7 @@ import java.nio.file.attribute.BasicFileAttributes;
|
|||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 知识库初始化服务
|
* 知识库初始化服务
|
||||||
@@ -193,12 +194,12 @@ public class KnowledgeBaseInitService {
|
|||||||
int indexSize = knowledgeIndexService.getIndexSize();
|
int indexSize = knowledgeIndexService.getIndexSize();
|
||||||
logger.debug("L0 索引大小: {}", indexSize);
|
logger.debug("L0 索引大小: {}", indexSize);
|
||||||
|
|
||||||
// 按分类统计(从 metadata JSON 中提取 category)
|
// 按分类统计(从 fault_source 字段读取)
|
||||||
Map<String, Long> categoryCount = new HashMap<>();
|
Map<String, Long> categoryCount = apiDocumentRepository.findAll().stream()
|
||||||
apiDocumentRepository.findAll().forEach(doc -> {
|
.collect(Collectors.groupingBy(
|
||||||
String category = extractCategoryFromMetadata(doc.getMetadata());
|
doc -> doc.getFaultSource() != null ? doc.getFaultSource() : "general",
|
||||||
categoryCount.merge(category, 1L, Long::sum);
|
Collectors.counting()
|
||||||
});
|
));
|
||||||
stats.setCategoryCount(categoryCount);
|
stats.setCategoryCount(categoryCount);
|
||||||
|
|
||||||
// Milvus 中的向量数量(需要实现)
|
// Milvus 中的向量数量(需要实现)
|
||||||
@@ -250,10 +251,9 @@ public class KnowledgeBaseInitService {
|
|||||||
document.setApiName(title); // 使用 title 作为 apiName
|
document.setApiName(title); // 使用 title 作为 apiName
|
||||||
document.setStatus("PENDING"); // 初始状态为 PENDING,索引成功后更新为 INDEXED
|
document.setStatus("PENDING"); // 初始状态为 PENDING,索引成功后更新为 INDEXED
|
||||||
|
|
||||||
// 尝试映射 category 到 FaultCategory(如果匹配的话)
|
// 直接使用 fault_source 字段存储 category(api, infrastructure, domain 等)
|
||||||
// 否则保持默认值 EXTERNAL_API
|
// fault_category 字段名虽然不太贴切,但可以用来存储文档分类
|
||||||
// 注意:frontmatter 的 category(api, infrastructure, domain)与 FaultCategory 枚举不完全匹配
|
document.setFaultSource(category);
|
||||||
// 真实的分类信息保存在 metadata 中
|
|
||||||
|
|
||||||
// 将 frontmatter 信息保存到 metadata(JSON 格式)
|
// 将 frontmatter 信息保存到 metadata(JSON 格式)
|
||||||
String metadataJson = String.format(
|
String metadataJson = String.format(
|
||||||
@@ -283,34 +283,6 @@ public class KnowledgeBaseInitService {
|
|||||||
.replace("\r", "\\r");
|
.replace("\r", "\\r");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从 metadata JSON 中提取 category
|
|
||||||
*/
|
|
||||||
private String extractCategoryFromMetadata(String metadata) {
|
|
||||||
if (metadata == null || metadata.isEmpty()) {
|
|
||||||
return "general";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 简单的 JSON 解析(提取 "category":"xxx")
|
|
||||||
int categoryIndex = metadata.indexOf("\"category\":\"");
|
|
||||||
if (categoryIndex == -1) {
|
|
||||||
return "general";
|
|
||||||
}
|
|
||||||
|
|
||||||
int startIndex = categoryIndex + "\"category\":\"".length();
|
|
||||||
int endIndex = metadata.indexOf("\"", startIndex);
|
|
||||||
if (endIndex == -1) {
|
|
||||||
return "general";
|
|
||||||
}
|
|
||||||
|
|
||||||
return metadata.substring(startIndex, endIndex);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("解析 metadata 失败: {}", metadata, e);
|
|
||||||
return "general";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提取文档正文(去除 frontmatter)
|
* 提取文档正文(去除 frontmatter)
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user