iconsulting/packages/services/knowledge-service/src/knowledge/knowledge.service.ts

337 lines
8.5 KiB
TypeScript

import { Injectable, Inject } from '@nestjs/common';
import { EmbeddingService } from '../infrastructure/embedding/embedding.service';
import { ChunkingService } from '../application/services/chunking.service';
import {
IKnowledgeRepository,
KNOWLEDGE_REPOSITORY,
} from '../domain/repositories/knowledge.repository.interface';
import {
KnowledgeArticleEntity,
KnowledgeSource,
} from '../domain/entities/knowledge-article.entity';
/**
* 知识管理服务
* 提供知识库的CRUD操作和处理
*/
@Injectable()
export class KnowledgeService {
constructor(
private embeddingService: EmbeddingService,
private chunkingService: ChunkingService,
@Inject(KNOWLEDGE_REPOSITORY)
private knowledgeRepo: IKnowledgeRepository,
) {}
/**
* 创建知识文章
*/
async createArticle(params: {
title: string;
content: string;
category: string;
tags?: string[];
source?: KnowledgeSource;
sourceUrl?: string;
createdBy?: string;
autoPublish?: boolean;
}): Promise<KnowledgeArticleEntity> {
// 1. 创建文章实体
const article = KnowledgeArticleEntity.create({
title: params.title,
content: params.content,
category: params.category,
tags: params.tags,
source: params.source || KnowledgeSource.MANUAL,
sourceUrl: params.sourceUrl,
createdBy: params.createdBy,
});
// 2. 生成文章向量
const embedding = await this.embeddingService.getEmbedding(
`${article.title}\n${article.summary}`,
);
article.setEmbedding(embedding);
// 3. 自动发布(可选)
if (params.autoPublish) {
article.publish();
}
// 4. 保存文章
await this.knowledgeRepo.saveArticle(article);
// 5. 分块并保存
await this.processArticleChunks(article);
console.log(`[KnowledgeService] Created article: ${article.id} - ${article.title}`);
return article;
}
/**
* 处理文章分块
*/
private async processArticleChunks(article: KnowledgeArticleEntity): Promise<void> {
// 1. 删除旧的块
await this.knowledgeRepo.deleteChunksByArticleId(article.id);
// 2. 分块
const chunks = this.chunkingService.chunkArticle(article);
// 3. 批量生成向量
const embeddings = await this.embeddingService.getEmbeddings(
chunks.map(c => c.content),
);
// 4. 设置向量
chunks.forEach((chunk, index) => {
chunk.setEmbedding(embeddings[index]);
});
// 5. 保存块
await this.knowledgeRepo.saveChunks(chunks);
console.log(`[KnowledgeService] Processed ${chunks.length} chunks for article ${article.id}`);
}
/**
* 更新文章
*/
async updateArticle(
articleId: string,
params: {
title?: string;
content?: string;
category?: string;
tags?: string[];
updatedBy?: string;
},
): Promise<KnowledgeArticleEntity> {
const article = await this.knowledgeRepo.findArticleById(articleId);
if (!article) {
throw new Error(`Article not found: ${articleId}`);
}
// 更新字段
if (params.title || params.content) {
article.updateContent(
params.title || article.title,
params.content || article.content,
params.updatedBy,
);
// 重新生成向量
const embedding = await this.embeddingService.getEmbedding(
`${article.title}\n${article.summary}`,
);
article.setEmbedding(embedding);
// 重新分块
await this.processArticleChunks(article);
}
if (params.category) {
article.category = params.category;
}
if (params.tags) {
article.tags = params.tags;
}
await this.knowledgeRepo.updateArticle(article);
return article;
}
/**
* 获取文章详情
*/
async getArticle(articleId: string): Promise<KnowledgeArticleEntity | null> {
return this.knowledgeRepo.findArticleById(articleId);
}
/**
* 获取文章列表
*/
async listArticles(params: {
category?: string;
publishedOnly?: boolean;
page?: number;
pageSize?: number;
}): Promise<{
items: KnowledgeArticleEntity[];
total: number;
page: number;
pageSize: number;
}> {
const page = params.page || 1;
const pageSize = params.pageSize || 20;
const offset = (page - 1) * pageSize;
const [items, total] = await Promise.all([
this.knowledgeRepo.findArticlesByCategory(params.category || '', {
publishedOnly: params.publishedOnly,
limit: pageSize,
offset,
}),
this.knowledgeRepo.countArticles({
category: params.category,
publishedOnly: params.publishedOnly,
}),
]);
return { items, total, page, pageSize };
}
/**
* 搜索文章
*/
async searchArticles(params: {
query: string;
category?: string;
useVector?: boolean;
}): Promise<Array<{
article: KnowledgeArticleEntity;
similarity?: number;
}>> {
if (params.useVector) {
// 向量搜索
const embedding = await this.embeddingService.getEmbedding(params.query);
const results = await this.knowledgeRepo.searchArticlesByVector(embedding, {
category: params.category,
publishedOnly: true,
limit: 10,
});
return results;
}
// 关键词搜索
const articles = await this.knowledgeRepo.searchArticles(params.query, {
category: params.category,
publishedOnly: true,
limit: 10,
});
return articles.map(article => ({ article }));
}
/**
* 发布文章
*/
async publishArticle(articleId: string): Promise<void> {
const article = await this.knowledgeRepo.findArticleById(articleId);
if (!article) {
throw new Error(`Article not found: ${articleId}`);
}
article.publish();
await this.knowledgeRepo.updateArticle(article);
}
/**
* 取消发布
*/
async unpublishArticle(articleId: string): Promise<void> {
const article = await this.knowledgeRepo.findArticleById(articleId);
if (!article) {
throw new Error(`Article not found: ${articleId}`);
}
article.unpublish();
await this.knowledgeRepo.updateArticle(article);
}
/**
* 删除文章
*/
async deleteArticle(articleId: string): Promise<void> {
// 先删除分块
await this.knowledgeRepo.deleteChunksByArticleId(articleId);
// 再删除文章
await this.knowledgeRepo.deleteArticle(articleId);
}
/**
* 记录用户反馈
*/
async recordFeedback(articleId: string, helpful: boolean): Promise<void> {
const article = await this.knowledgeRepo.findArticleById(articleId);
if (!article) {
throw new Error(`Article not found: ${articleId}`);
}
article.recordFeedback(helpful);
await this.knowledgeRepo.updateArticle(article);
}
/**
* 批量导入文章
*/
async importArticles(
articles: Array<{
title: string;
content: string;
category: string;
tags?: string[];
}>,
createdBy?: string,
): Promise<{ success: number; failed: number; errors: string[] }> {
let success = 0;
let failed = 0;
const errors: string[] = [];
for (const articleData of articles) {
try {
await this.createArticle({
...articleData,
source: KnowledgeSource.IMPORT,
createdBy,
autoPublish: false, // 导入后需要审核
});
success++;
} catch (error) {
failed++;
errors.push(`Failed to import "${articleData.title}": ${(error as Error).message}`);
}
}
return { success, failed, errors };
}
/**
* 获取知识库统计
*/
async getStatistics(): Promise<{
totalArticles: number;
publishedArticles: number;
byCategory: Record<string, number>;
recentArticles: KnowledgeArticleEntity[];
}> {
const categories = ['QMAS', 'GEP', 'IANG', 'TTPS', 'CIES', 'TechTAS', 'GENERAL'];
const [total, published, byCategoryResults, recent] = await Promise.all([
this.knowledgeRepo.countArticles(),
this.knowledgeRepo.countArticles({ publishedOnly: true }),
Promise.all(
categories.map(async cat => ({
category: cat,
count: await this.knowledgeRepo.countArticles({ category: cat }),
})),
),
this.knowledgeRepo.findArticlesByCategory('', { limit: 5 }),
]);
const byCategory: Record<string, number> = {};
byCategoryResults.forEach(r => {
byCategory[r.category] = r.count;
});
return {
totalArticles: total,
publishedArticles: published,
byCategory,
recentArticles: recent,
};
}
}