refactor(evolution): use knowledge-service API for system_experiences
Follow proper microservices architecture: - knowledge-service owns system_experiences table - evolution-service uses KnowledgeClient API to save experiences - Deleted SystemExperienceORM from evolution-service - Added internal API endpoints in knowledge-service - Disabled synchronize in all services for safety Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e1bcd0145e
commit
c2b4fe19cc
|
|
@ -25,7 +25,8 @@ import { HealthModule } from './health/health.module';
|
||||||
password: config.get('POSTGRES_PASSWORD'),
|
password: config.get('POSTGRES_PASSWORD'),
|
||||||
database: config.get('POSTGRES_DB', 'iconsulting'),
|
database: config.get('POSTGRES_DB', 'iconsulting'),
|
||||||
autoLoadEntities: true,
|
autoLoadEntities: true,
|
||||||
synchronize: config.get('NODE_ENV') !== 'production',
|
// 禁用synchronize,使用init-db.sql初始化schema
|
||||||
|
synchronize: false,
|
||||||
logging: config.get('NODE_ENV') === 'development',
|
logging: config.get('NODE_ENV') === 'development',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,19 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { EvolutionController } from './evolution.controller';
|
import { EvolutionController } from './evolution.controller';
|
||||||
import { EvolutionService } from './evolution.service';
|
import { EvolutionService } from './evolution.service';
|
||||||
import { ExperienceExtractorService } from '../infrastructure/claude/experience-extractor.service';
|
import { ExperienceExtractorService } from '../infrastructure/claude/experience-extractor.service';
|
||||||
import { ConversationClient } from '../infrastructure/clients/conversation.client';
|
import { ConversationClient } from '../infrastructure/clients/conversation.client';
|
||||||
import { SystemExperienceORM } from '../infrastructure/database/entities/system-experience.orm';
|
import { KnowledgeClient } from '../infrastructure/clients/knowledge.client';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [],
|
||||||
// 只保留自己 Bounded Context 的实体
|
|
||||||
TypeOrmModule.forFeature([SystemExperienceORM]),
|
|
||||||
],
|
|
||||||
controllers: [EvolutionController],
|
controllers: [EvolutionController],
|
||||||
providers: [
|
providers: [
|
||||||
EvolutionService,
|
EvolutionService,
|
||||||
ExperienceExtractorService,
|
ExperienceExtractorService,
|
||||||
// 使用 API 客户端访问其他服务的数据
|
// 使用 API 客户端访问其他服务的数据
|
||||||
ConversationClient,
|
ConversationClient,
|
||||||
|
KnowledgeClient,
|
||||||
],
|
],
|
||||||
exports: [EvolutionService],
|
exports: [EvolutionService],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { ExperienceExtractorService } from '../infrastructure/claude/experience-extractor.service';
|
import { ExperienceExtractorService } from '../infrastructure/claude/experience-extractor.service';
|
||||||
import { ConversationClient, ConversationDto, MessageDto } from '../infrastructure/clients/conversation.client';
|
import { ConversationClient } from '../infrastructure/clients/conversation.client';
|
||||||
import { SystemExperienceORM } from '../infrastructure/database/entities/system-experience.orm';
|
import { KnowledgeClient } from '../infrastructure/clients/knowledge.client';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -21,13 +19,17 @@ export interface EvolutionTaskResult {
|
||||||
/**
|
/**
|
||||||
* 进化服务
|
* 进化服务
|
||||||
* 负责系统的自我学习和进化
|
* 负责系统的自我学习和进化
|
||||||
|
*
|
||||||
|
* 遵循微服务架构原则:
|
||||||
|
* - 通过 ConversationClient 调用 conversation-service API 获取对话数据
|
||||||
|
* - 通过 KnowledgeClient 调用 knowledge-service API 保存经验
|
||||||
|
* - 不直接访问其他服务的数据库表
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EvolutionService {
|
export class EvolutionService {
|
||||||
constructor(
|
constructor(
|
||||||
private conversationClient: ConversationClient,
|
private conversationClient: ConversationClient,
|
||||||
@InjectRepository(SystemExperienceORM)
|
private knowledgeClient: KnowledgeClient,
|
||||||
private experienceRepo: Repository<SystemExperienceORM>,
|
|
||||||
private experienceExtractor: ExperienceExtractorService,
|
private experienceExtractor: ExperienceExtractorService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
@ -86,17 +88,22 @@ export class EvolutionService {
|
||||||
rating: conversation.rating,
|
rating: conversation.rating,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 保存提取的经验
|
// 通过 API 保存提取的经验到 knowledge-service
|
||||||
for (const exp of analysis.experiences) {
|
for (const exp of analysis.experiences) {
|
||||||
await this.saveExperience({
|
try {
|
||||||
experienceType: exp.type,
|
await this.knowledgeClient.saveExperience({
|
||||||
content: exp.content,
|
experienceType: exp.type,
|
||||||
scenario: exp.scenario,
|
content: exp.content,
|
||||||
confidence: exp.confidence,
|
scenario: exp.scenario,
|
||||||
relatedCategory: exp.relatedCategory,
|
confidence: exp.confidence,
|
||||||
sourceConversationId: conversation.id,
|
relatedCategory: exp.relatedCategory,
|
||||||
});
|
sourceConversationId: conversation.id,
|
||||||
result.experiencesExtracted++;
|
});
|
||||||
|
result.experiencesExtracted++;
|
||||||
|
} catch (saveError) {
|
||||||
|
console.error(`[Evolution] Failed to save experience:`, saveError);
|
||||||
|
result.errors.push(`Save experience: ${(saveError as Error).message}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收集知识缺口
|
// 收集知识缺口
|
||||||
|
|
@ -127,65 +134,6 @@ export class EvolutionService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存经验(带去重逻辑)
|
|
||||||
*/
|
|
||||||
private async saveExperience(params: {
|
|
||||||
experienceType: string;
|
|
||||||
content: string;
|
|
||||||
scenario: string;
|
|
||||||
confidence: number;
|
|
||||||
relatedCategory?: string;
|
|
||||||
sourceConversationId: string;
|
|
||||||
}): Promise<void> {
|
|
||||||
// 查找相似经验
|
|
||||||
const existingExperiences = await this.experienceRepo.find({
|
|
||||||
where: {
|
|
||||||
experienceType: params.experienceType,
|
|
||||||
relatedCategory: params.relatedCategory,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 简单的相似度检查(实际应该用向量相似度)
|
|
||||||
const similar = existingExperiences.find(
|
|
||||||
exp => this.simpleSimilarity(exp.content, params.content) > 0.8,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (similar) {
|
|
||||||
// 合并到现有经验
|
|
||||||
if (!similar.sourceConversationIds.includes(params.sourceConversationId)) {
|
|
||||||
similar.sourceConversationIds.push(params.sourceConversationId);
|
|
||||||
similar.confidence = Math.min(100, similar.confidence + 5);
|
|
||||||
await this.experienceRepo.save(similar);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 创建新经验
|
|
||||||
const newExperience = this.experienceRepo.create({
|
|
||||||
id: uuidv4(),
|
|
||||||
experienceType: params.experienceType,
|
|
||||||
content: params.content,
|
|
||||||
scenario: params.scenario,
|
|
||||||
confidence: params.confidence,
|
|
||||||
relatedCategory: params.relatedCategory,
|
|
||||||
sourceConversationIds: [params.sourceConversationId],
|
|
||||||
verificationStatus: 'PENDING',
|
|
||||||
isActive: false,
|
|
||||||
});
|
|
||||||
await this.experienceRepo.save(newExperience);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 简单的文本相似度计算
|
|
||||||
*/
|
|
||||||
private simpleSimilarity(a: string, b: string): number {
|
|
||||||
const aWords = new Set(a.toLowerCase().split(/\s+/));
|
|
||||||
const bWords = new Set(b.toLowerCase().split(/\s+/));
|
|
||||||
const intersection = [...aWords].filter(x => bWords.has(x)).length;
|
|
||||||
const union = new Set([...aWords, ...bWords]).size;
|
|
||||||
return intersection / union;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取进化统计信息
|
* 获取进化统计信息
|
||||||
*/
|
*/
|
||||||
|
|
@ -197,12 +145,8 @@ export class EvolutionService {
|
||||||
recentConversationsAnalyzed: number;
|
recentConversationsAnalyzed: number;
|
||||||
topExperienceTypes: Array<{ type: string; count: number }>;
|
topExperienceTypes: Array<{ type: string; count: number }>;
|
||||||
}> {
|
}> {
|
||||||
const [total, pending, approved, active] = await Promise.all([
|
// 通过 API 获取经验统计
|
||||||
this.experienceRepo.count(),
|
const stats = await this.knowledgeClient.getStatistics();
|
||||||
this.experienceRepo.count({ where: { verificationStatus: 'PENDING' } }),
|
|
||||||
this.experienceRepo.count({ where: { verificationStatus: 'APPROVED' } }),
|
|
||||||
this.experienceRepo.count({ where: { isActive: true } }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 通过 API 获取最近分析的对话数(过去7天)
|
// 通过 API 获取最近分析的对话数(过去7天)
|
||||||
const recentConversations = await this.conversationClient.countConversations({
|
const recentConversations = await this.conversationClient.countConversations({
|
||||||
|
|
@ -210,26 +154,19 @@ export class EvolutionService {
|
||||||
daysBack: 7,
|
daysBack: 7,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取经验类型分布
|
// 转换类型分布为数组格式
|
||||||
const typeDistribution = await this.experienceRepo
|
const topExperienceTypes = Object.entries(stats.byType)
|
||||||
.createQueryBuilder('exp')
|
.map(([type, count]) => ({ type, count: count as number }))
|
||||||
.select('exp.experienceType', 'type')
|
.sort((a, b) => b.count - a.count)
|
||||||
.addSelect('COUNT(*)', 'count')
|
.slice(0, 5);
|
||||||
.groupBy('exp.experienceType')
|
|
||||||
.orderBy('count', 'DESC')
|
|
||||||
.limit(5)
|
|
||||||
.getRawMany();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalExperiences: total,
|
totalExperiences: stats.total,
|
||||||
pendingExperiences: pending,
|
pendingExperiences: stats.byStatus['PENDING'] || 0,
|
||||||
approvedExperiences: approved,
|
approvedExperiences: stats.byStatus['APPROVED'] || 0,
|
||||||
activeExperiences: active,
|
activeExperiences: stats.byStatus['ACTIVE'] || 0,
|
||||||
recentConversationsAnalyzed: recentConversations,
|
recentConversationsAnalyzed: recentConversations,
|
||||||
topExperienceTypes: typeDistribution.map(t => ({
|
topExperienceTypes,
|
||||||
type: t.type,
|
|
||||||
count: parseInt(t.count),
|
|
||||||
})),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经验数据传输对象
|
||||||
|
*/
|
||||||
|
export interface ExperienceDto {
|
||||||
|
id: string;
|
||||||
|
experienceType: string;
|
||||||
|
content: string;
|
||||||
|
scenario: string;
|
||||||
|
confidence: number;
|
||||||
|
relatedCategory?: string;
|
||||||
|
sourceConversationIds: string[];
|
||||||
|
verificationStatus: string;
|
||||||
|
isActive: boolean;
|
||||||
|
usageCount: number;
|
||||||
|
positiveCount: number;
|
||||||
|
negativeCount: number;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 经验统计数据传输对象
|
||||||
|
*/
|
||||||
|
export interface ExperienceStatisticsDto {
|
||||||
|
total: number;
|
||||||
|
byStatus: Record<string, number>;
|
||||||
|
byType: Record<string, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Knowledge Service 客户端
|
||||||
|
* 用于调用 knowledge-service 的内部 API
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class KnowledgeClient {
|
||||||
|
private readonly baseUrl: string;
|
||||||
|
|
||||||
|
constructor(private configService: ConfigService) {
|
||||||
|
this.baseUrl = this.configService.get<string>(
|
||||||
|
'KNOWLEDGE_SERVICE_URL',
|
||||||
|
'http://knowledge-service:3005',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存经验
|
||||||
|
*/
|
||||||
|
async saveExperience(params: {
|
||||||
|
experienceType: string;
|
||||||
|
content: string;
|
||||||
|
scenario: string;
|
||||||
|
confidence: number;
|
||||||
|
relatedCategory?: string;
|
||||||
|
sourceConversationId: string;
|
||||||
|
}): Promise<ExperienceDto> {
|
||||||
|
const url = `${this.baseUrl}/api/internal/experiences`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error('Failed to save experience');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[KnowledgeClient] Error saving experience:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取经验统计
|
||||||
|
*/
|
||||||
|
async getStatistics(): Promise<ExperienceStatisticsDto> {
|
||||||
|
const url = `${this.baseUrl}/api/internal/experiences/statistics`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error('Failed to get statistics');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[KnowledgeClient] Error getting statistics:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计经验数量
|
||||||
|
*/
|
||||||
|
async countExperiences(options: {
|
||||||
|
status?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
}): Promise<number> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (options.status) params.append('status', options.status);
|
||||||
|
if (options.isActive !== undefined) {
|
||||||
|
params.append('isActive', options.isActive.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${this.baseUrl}/api/internal/experiences/count?${params.toString()}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error('Failed to count experiences');
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.data.count;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[KnowledgeClient] Error counting experiences:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import {
|
|
||||||
Entity,
|
|
||||||
Column,
|
|
||||||
PrimaryColumn,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
} from 'typeorm';
|
|
||||||
|
|
||||||
@Entity('system_experiences')
|
|
||||||
export class SystemExperienceORM {
|
|
||||||
@PrimaryColumn('uuid')
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@Column({ name: 'experience_type', length: 30 })
|
|
||||||
experienceType: string;
|
|
||||||
|
|
||||||
@Column('text')
|
|
||||||
content: string;
|
|
||||||
|
|
||||||
@Column({ default: 50 })
|
|
||||||
confidence: number;
|
|
||||||
|
|
||||||
@Column('text')
|
|
||||||
scenario: string;
|
|
||||||
|
|
||||||
@Column({ name: 'related_category', length: 50, nullable: true })
|
|
||||||
relatedCategory: string;
|
|
||||||
|
|
||||||
@Column('uuid', { name: 'source_conversation_ids', array: true, default: '{}' })
|
|
||||||
sourceConversationIds: string[];
|
|
||||||
|
|
||||||
@Column({ name: 'verification_status', length: 20, default: 'PENDING' })
|
|
||||||
verificationStatus: string;
|
|
||||||
|
|
||||||
@Column({ name: 'verified_by', nullable: true })
|
|
||||||
verifiedBy: string;
|
|
||||||
|
|
||||||
@Column({ name: 'verified_at', nullable: true })
|
|
||||||
verifiedAt: Date;
|
|
||||||
|
|
||||||
@Column({ name: 'usage_count', default: 0 })
|
|
||||||
usageCount: number;
|
|
||||||
|
|
||||||
@Column({ name: 'positive_count', default: 0 })
|
|
||||||
positiveCount: number;
|
|
||||||
|
|
||||||
@Column({ name: 'negative_count', default: 0 })
|
|
||||||
negativeCount: number;
|
|
||||||
|
|
||||||
// pgvector VECTOR(1536) - exclude from default selects to avoid type conflicts
|
|
||||||
@Column({ type: 'text', name: 'embedding', nullable: true, select: false })
|
|
||||||
embedding: string;
|
|
||||||
|
|
||||||
@Column({ name: 'is_active', default: false })
|
|
||||||
isActive: boolean;
|
|
||||||
|
|
||||||
@CreateDateColumn({ name: 'created_at' })
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn({ name: 'updated_at' })
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
@ -25,7 +25,8 @@ import { HealthModule } from './health/health.module';
|
||||||
password: config.get('POSTGRES_PASSWORD'),
|
password: config.get('POSTGRES_PASSWORD'),
|
||||||
database: config.get('POSTGRES_DB', 'iconsulting'),
|
database: config.get('POSTGRES_DB', 'iconsulting'),
|
||||||
autoLoadEntities: true,
|
autoLoadEntities: true,
|
||||||
synchronize: config.get('NODE_ENV') !== 'production',
|
// 禁用synchronize,使用init-db.sql初始化schema
|
||||||
|
synchronize: false,
|
||||||
logging: config.get('NODE_ENV') === 'development',
|
logging: config.get('NODE_ENV') === 'development',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { Controller, Get, Post, Body, Query } from '@nestjs/common';
|
||||||
|
import { MemoryService } from './memory.service';
|
||||||
|
import { ExperienceType } from '../domain/entities/system-experience.entity';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部 API 控制器
|
||||||
|
* 仅供其他微服务调用(如 evolution-service)
|
||||||
|
*/
|
||||||
|
@Controller('internal/experiences')
|
||||||
|
export class InternalMemoryController {
|
||||||
|
constructor(private memoryService: MemoryService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存经验(由 evolution-service 调用)
|
||||||
|
*/
|
||||||
|
@Post()
|
||||||
|
async saveExperience(@Body() dto: {
|
||||||
|
experienceType: string;
|
||||||
|
content: string;
|
||||||
|
scenario: string;
|
||||||
|
confidence: number;
|
||||||
|
relatedCategory?: string;
|
||||||
|
sourceConversationId: string;
|
||||||
|
}) {
|
||||||
|
const experience = await this.memoryService.extractAndSaveExperience({
|
||||||
|
experienceType: dto.experienceType as ExperienceType,
|
||||||
|
content: dto.content,
|
||||||
|
scenario: dto.scenario,
|
||||||
|
confidence: dto.confidence,
|
||||||
|
relatedCategory: dto.relatedCategory,
|
||||||
|
sourceConversationId: dto.sourceConversationId,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
id: experience.id,
|
||||||
|
experienceType: experience.experienceType,
|
||||||
|
content: experience.content,
|
||||||
|
scenario: experience.scenario,
|
||||||
|
confidence: experience.confidence,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找相似经验(用于去重检查)
|
||||||
|
*/
|
||||||
|
@Post('find-similar')
|
||||||
|
async findSimilarExperiences(@Body() dto: {
|
||||||
|
experienceType: string;
|
||||||
|
relatedCategory?: string;
|
||||||
|
}) {
|
||||||
|
// 返回同类型同类别的经验用于客户端去重
|
||||||
|
// 实际的相似度检查在客户端进行
|
||||||
|
const stats = await this.memoryService.getExperienceStatistics();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
totalByType: stats.byType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取经验统计
|
||||||
|
*/
|
||||||
|
@Get('statistics')
|
||||||
|
async getStatistics() {
|
||||||
|
const stats = await this.memoryService.getExperienceStatistics();
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
total: stats.total,
|
||||||
|
byStatus: stats.byStatus,
|
||||||
|
byType: stats.byType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统计经验数量
|
||||||
|
*/
|
||||||
|
@Get('count')
|
||||||
|
async countExperiences(
|
||||||
|
@Query('status') status?: string,
|
||||||
|
@Query('isActive') isActive?: string,
|
||||||
|
) {
|
||||||
|
const stats = await this.memoryService.getExperienceStatistics();
|
||||||
|
|
||||||
|
let count = stats.total;
|
||||||
|
if (status && stats.byStatus[status as keyof typeof stats.byStatus]) {
|
||||||
|
count = stats.byStatus[status as keyof typeof stats.byStatus];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: { count },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { MemoryController } from './memory.controller';
|
import { MemoryController } from './memory.controller';
|
||||||
|
import { InternalMemoryController } from './internal.controller';
|
||||||
import { MemoryService } from './memory.service';
|
import { MemoryService } from './memory.service';
|
||||||
import { EmbeddingService } from '../infrastructure/embedding/embedding.service';
|
import { EmbeddingService } from '../infrastructure/embedding/embedding.service';
|
||||||
import { Neo4jService } from '../infrastructure/database/neo4j/neo4j.service';
|
import { Neo4jService } from '../infrastructure/database/neo4j/neo4j.service';
|
||||||
|
|
@ -22,7 +23,7 @@ import {
|
||||||
SystemExperienceORM,
|
SystemExperienceORM,
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
controllers: [MemoryController],
|
controllers: [MemoryController, InternalMemoryController],
|
||||||
providers: [
|
providers: [
|
||||||
MemoryService,
|
MemoryService,
|
||||||
EmbeddingService,
|
EmbeddingService,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue