Files
AIclinicalresearch/知识库需求调整说明.md
AI Clinical Dev Team 239c7ea85e feat: Day 21-22 - knowledge base frontend completed, fix CORS and file upload issues
- Complete knowledge base list and detail pages
- Complete document upload component
- Fix CORS config (add PUT/DELETE method support)
- Fix file upload issues (disabled state and beforeUpload return value)
- Add detailed debug logs (cleaned up)
- Create Day 21-22 completion summary document
2025-10-11 15:40:12 +08:00

572 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 知识库需求调整说明
## 📋 需求变更
### 原需求PRD原文
> "后期考虑增加基于大规模1000篇以内文献的读取、识别、内容提取的工作"
**理解:** 这给人的印象是需要处理海量文献,技术难度极高。
### 实际需求(明确后)
**每个用户最多创建 3个知识库**
**每个知识库最多上传 50个文件**
**主要格式PDF、DOCX**
**单用户最大文档量150个文件**
---
## 🎯 影响分析
### 技术难度大幅降低
| 维度 | 原理解1000篇+ | 实际需求150个/用户) | 影响 |
|------|-----------------|---------------------|------|
| **向量数据库** | 需要高性能集群 | Dify内置Qdrant足够 | ✅ 简化 |
| **文档处理** | 需要分布式处理 | 单机异步处理即可 | ✅ 简化 |
| **检索性能** | 需要优化索引 | 默认配置即可 | ✅ 简化 |
| **存储成本** | 需要大容量存储 | 标准对象存储 | ✅ 降低 |
| **技术难度** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ✅ 降低 |
### Dify完全够用
**Dify的能力**
- ✅ 单个知识库支持上万个文档片段
- ✅ 自动文档解析PDF、Word、TXT等
- ✅ 内置向量化支持多种Embedding模型
- ✅ 混合检索(关键词 + 语义检索)
- ✅ 重排序Reranking
- ✅ 答案溯源
**我们的需求:**
- 50个文件/知识库远低于Dify上限
- PDF、DOCX格式Dify原生支持
- **结论Dify完全满足需求**
---
## 💾 数据库设计
### 知识库表结构
```sql
-- 知识库表
CREATE TABLE knowledge_bases (
id VARCHAR(50) PRIMARY KEY,
user_id VARCHAR(50) NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT,
-- Dify知识库ID
dify_dataset_id VARCHAR(100) NOT NULL,
-- 统计信息
file_count INT DEFAULT 0,
total_size_bytes BIGINT DEFAULT 0,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
FOREIGN KEY (user_id) REFERENCES users(id),
-- 限制每个用户最多3个知识库
CONSTRAINT check_kb_limit CHECK (
(SELECT COUNT(*) FROM knowledge_bases WHERE user_id = NEW.user_id) <= 3
)
);
-- 为用户ID创建索引
CREATE INDEX idx_kb_user ON knowledge_bases(user_id);
-- 文档表
CREATE TABLE documents (
id VARCHAR(50) PRIMARY KEY,
kb_id VARCHAR(50) NOT NULL,
user_id VARCHAR(50) NOT NULL,
filename VARCHAR(255) NOT NULL,
file_type VARCHAR(20) NOT NULL, -- pdf, docx
file_size_bytes BIGINT NOT NULL,
file_url TEXT NOT NULL, -- 对象存储URL
-- Dify文档ID
dify_document_id VARCHAR(100) NOT NULL,
-- 处理状态
status VARCHAR(20) DEFAULT 'uploading', -- uploading, processing, completed, failed
progress INT DEFAULT 0, -- 0-100
error_message TEXT,
-- 处理结果
segments_count INT DEFAULT 0, -- 切分的段落数
tokens_count INT DEFAULT 0, -- token数量
uploaded_at TIMESTAMP DEFAULT NOW(),
processed_at TIMESTAMP,
FOREIGN KEY (kb_id) REFERENCES knowledge_bases(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id),
-- 限制每个知识库最多50个文件
CONSTRAINT check_doc_limit CHECK (
(SELECT COUNT(*) FROM documents WHERE kb_id = NEW.kb_id) <= 50
)
);
-- 索引
CREATE INDEX idx_doc_kb ON documents(kb_id);
CREATE INDEX idx_doc_user ON documents(user_id);
CREATE INDEX idx_doc_status ON documents(status);
```
### 用户配额表
```sql
-- 用户配额表(可选,用于更灵活的配额管理)
CREATE TABLE user_quotas (
user_id VARCHAR(50) PRIMARY KEY,
-- 知识库配额
kb_quota INT DEFAULT 3, -- 允许创建的知识库数量
kb_used INT DEFAULT 0, -- 已使用
-- 文件配额(每个知识库)
files_per_kb_quota INT DEFAULT 50,
-- 存储配额(字节)
storage_quota_bytes BIGINT DEFAULT 1073741824, -- 1GB
storage_used_bytes BIGINT DEFAULT 0,
updated_at TIMESTAMP DEFAULT NOW(),
FOREIGN KEY (user_id) REFERENCES users(id)
);
```
---
## 🔧 实现方案
### 1. 知识库创建(带限制检查)
```typescript
// backend/src/services/knowledge-base.service.ts
export class KnowledgeBaseService {
/**
* 创建知识库
*/
async createKnowledgeBase(userId: string, data: CreateKBDto) {
// 1. 检查用户知识库数量限制
const userKbCount = await db.knowledge_bases.count({
where: { user_id: userId }
});
if (userKbCount >= 3) {
throw new Error('已达到知识库数量上限3个');
}
// 2. 在Dify中创建知识库
const difyDataset = await difyClient.createDataset({
name: data.name,
description: data.description,
indexing_technique: 'high_quality', // 高质量索引
permission: 'only_me'
});
// 3. 在数据库中保存记录
const kb = await db.knowledge_bases.create({
id: generateId('kb'),
user_id: userId,
name: data.name,
description: data.description,
dify_dataset_id: difyDataset.id,
file_count: 0,
total_size_bytes: 0
});
return kb;
}
/**
* 上传文档
*/
async uploadDocument(userId: string, kbId: string, file: File) {
// 1. 检查知识库是否存在且属于该用户
const kb = await db.knowledge_bases.findOne({
where: { id: kbId, user_id: userId }
});
if (!kb) {
throw new Error('知识库不存在或无权限');
}
// 2. 检查文件数量限制
const docCount = await db.documents.count({
where: { kb_id: kbId }
});
if (docCount >= 50) {
throw new Error('知识库文件数量已达上限50个');
}
// 3. 检查文件格式
const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
if (!allowedTypes.includes(file.mimetype)) {
throw new Error('仅支持PDF和DOCX格式');
}
// 4. 检查文件大小单个文件最大50MB
const maxSize = 50 * 1024 * 1024; // 50MB
if (file.size > maxSize) {
throw new Error('文件大小超过限制最大50MB');
}
// 5. 上传到对象存储
const fileUrl = await objectStorage.upload(file);
// 6. 创建文档记录
const doc = await db.documents.create({
id: generateId('doc'),
kb_id: kbId,
user_id: userId,
filename: file.originalname,
file_type: path.extname(file.originalname).slice(1),
file_size_bytes: file.size,
file_url: fileUrl,
status: 'uploading',
dify_document_id: '' // 稍后更新
});
// 7. 异步提交到Dify处理
this.processDocumentAsync(doc.id, kb.dify_dataset_id, fileUrl);
return doc;
}
/**
* 异步处理文档
*/
private async processDocumentAsync(docId: string, difyDatasetId: string, fileUrl: string) {
try {
// 1. 更新状态为处理中
await db.documents.update({
where: { id: docId },
data: { status: 'processing' }
});
// 2. 提交到Dify处理
const difyDoc = await difyClient.uploadDocument({
dataset_id: difyDatasetId,
file_url: fileUrl,
indexing_technique: 'high_quality',
process_rule: {
mode: 'automatic',
rules: {
pre_processing_rules: [
{ id: 'remove_extra_spaces', enabled: true },
{ id: 'remove_urls_emails', enabled: false } // 保留医学文献中的引用
],
segmentation: {
separator: '\n\n',
max_tokens: 500 // 每段最大500 tokens
}
}
}
});
// 3. 保存Dify文档ID
await db.documents.update({
where: { id: docId },
data: { dify_document_id: difyDoc.id }
});
// 4. 轮询处理状态
let status = 'processing';
while (status === 'processing') {
await sleep(2000); // 2秒后重试
const result = await difyClient.getDocumentStatus({
dataset_id: difyDatasetId,
document_id: difyDoc.id
});
status = result.indexing_status;
// 更新进度
await db.documents.update({
where: { id: docId },
data: {
progress: Math.round((result.completed_segments / result.total_segments) * 100)
}
});
}
// 5. 处理完成
if (status === 'completed') {
const result = await difyClient.getDocumentStatus({
dataset_id: difyDatasetId,
document_id: difyDoc.id
});
await db.documents.update({
where: { id: docId },
data: {
status: 'completed',
progress: 100,
segments_count: result.total_segments,
tokens_count: result.tokens,
processed_at: new Date()
}
});
// 更新知识库统计
await this.updateKbStats(docId);
} else {
// 处理失败
await db.documents.update({
where: { id: docId },
data: {
status: 'failed',
error_message: result.error || '文档处理失败'
}
});
}
} catch (error) {
// 异常处理
await db.documents.update({
where: { id: docId },
data: {
status: 'failed',
error_message: error.message
}
});
}
}
/**
* 检索知识库
*/
async queryKnowledgeBase(userId: string, kbId: string, query: string) {
// 1. 验证权限
const kb = await db.knowledge_bases.findOne({
where: { id: kbId, user_id: userId }
});
if (!kb) {
throw new Error('知识库不存在或无权限');
}
// 2. 调用Dify检索
const results = await difyClient.queryKnowledgeBase({
dataset_id: kb.dify_dataset_id,
query,
retrieval_model: {
search_method: 'hybrid_search', // 混合检索
reranking_enable: true, // 启用重排序
top_k: 5, // 返回前5个结果
score_threshold: 0.5 // 相似度阈值
}
});
// 3. 格式化返回结果
return results.records.map(record => ({
content: record.segment.content,
score: record.score,
document: {
id: record.segment.document.id,
name: record.segment.document.name,
position: record.segment.position // 在文档中的位置
}
}));
}
/**
* 获取用户的知识库列表
*/
async getUserKnowledgeBases(userId: string) {
const kbs = await db.knowledge_bases.findMany({
where: { user_id: userId },
include: {
_count: {
select: { documents: true }
}
}
});
return kbs.map(kb => ({
id: kb.id,
name: kb.name,
description: kb.description,
file_count: kb.file_count,
total_size_mb: (kb.total_size_bytes / 1024 / 1024).toFixed(2),
created_at: kb.created_at,
quota: {
used: kb.file_count,
limit: 50
}
}));
}
/**
* 删除文档
*/
async deleteDocument(userId: string, docId: string) {
// 1. 验证权限
const doc = await db.documents.findOne({
where: { id: docId, user_id: userId },
include: { knowledge_base: true }
});
if (!doc) {
throw new Error('文档不存在或无权限');
}
// 2. 从Dify删除
await difyClient.deleteDocument({
dataset_id: doc.knowledge_base.dify_dataset_id,
document_id: doc.dify_document_id
});
// 3. 从对象存储删除
await objectStorage.delete(doc.file_url);
// 4. 从数据库删除
await db.documents.delete({ where: { id: docId } });
// 5. 更新知识库统计
await this.updateKbStats(doc.kb_id);
}
/**
* 更新知识库统计信息
*/
private async updateKbStats(kbId: string) {
const stats = await db.documents.aggregate({
where: { kb_id: kbId, status: 'completed' },
_count: true,
_sum: { file_size_bytes: true }
});
await db.knowledge_bases.update({
where: { id: kbId },
data: {
file_count: stats._count,
total_size_bytes: stats._sum.file_size_bytes || 0
}
});
}
}
```
---
## 📊 成本影响
### 存储成本(降低)
**原估算1000篇文献**
- 假设每篇10MB
- 总存储10GB+
- 成本¥200+/月
**实际需求150个文件/用户):**
```
单用户存储:
- 150个文件 × 平均5MB = 750MB
- 1000个用户 = 750GB
月度成本阿里云OSS
- 存储750GB × ¥0.12/GB = ¥90/月
- 流量假设100GB/月 × ¥0.5/GB = ¥50/月
- 总计¥140/月
```
**成本节省约¥60/月**
### 处理性能(提升)
**小规模知识库的优势:**
- ✅ 检索速度更快(< 1s
- ✅ 向量化处理更快(单文件 < 30s
- ✅ 无需复杂的性能优化
---
## 🎯 技术选型确认
### Dify完全满足需求 ✅
| 功能 | 需求 | Dify能力 | 结论 |
|------|------|---------|------|
| 知识库数量 | 3个/用户 | 无限制 | ✅ 满足 |
| 文件数量 | 50个/知识库 | 上千个 | ✅ 满足 |
| 文件格式 | PDF、DOCX | 支持20+格式 | ✅ 满足 |
| 向量化 | 自动 | 内置支持 | ✅ 满足 |
| 检索 | 混合检索 | 支持 | ✅ 满足 |
| 答案溯源 | 需要 | 支持 | ✅ 满足 |
**结论无需考虑其他RAG方案Dify完全够用**
---
## 📝 API设计
### RESTful API
```typescript
// 知识库管理
GET /api/knowledge-bases // 获取用户的知识库列表
POST /api/knowledge-bases // 创建知识库
GET /api/knowledge-bases/:id // 获取知识库详情
PUT /api/knowledge-bases/:id // 更新知识库
DELETE /api/knowledge-bases/:id // 删除知识库
// 文档管理
GET /api/knowledge-bases/:kbId/documents // 获取文档列表
POST /api/knowledge-bases/:kbId/documents // 上传文档
GET /api/knowledge-bases/:kbId/documents/:docId // 获取文档详情
DELETE /api/knowledge-bases/:kbId/documents/:docId // 删除文档
// 检索
POST /api/knowledge-bases/:kbId/query // 检索知识库
// 配额查询
GET /api/users/me/quotas // 获取用户配额信息
```
---
## ✅ 总结
### 需求明确后的影响
| 方面 | 原理解 | 实际需求 | 影响 |
|------|--------|---------|------|
| **技术难度** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 大幅降低 |
| **开发成本** | 需自建RAG | 用Dify即可 | 节省30天+ |
| **运营成本** | ¥200+/月 | ¥140/月 | 节省30% |
| **性能要求** | 需要优化 | 默认配置 | 简化 |
| **风险** | 高 | 低 | 降低 |
### 最终方案
**✅ 使用Dify处理知识库完全满足需求**
- 每用户3个知识库
- 每知识库50个文件
- PDF、DOCX格式
- 自动向量化、检索、答案溯源
**无需考虑其他RAG方案**
---
**文档版本v1.0**
**更新时间2025-10-10**