memento-mcp-server 1.16.1 → 1.16.2-a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +4 -3
- package/dist/client/index.js.map +1 -1
- package/dist/domains/anchor/services/anchor/anchor-interfaces.d.ts +2 -4
- package/dist/domains/anchor/services/anchor/anchor-interfaces.d.ts.map +1 -1
- package/dist/domains/anchor/services/anchor/anchor-interfaces.js.map +1 -1
- package/dist/domains/anchor/services/anchor/anchor-manager.d.ts +2 -2
- package/dist/domains/anchor/services/anchor/anchor-manager.d.ts.map +1 -1
- package/dist/domains/anchor/services/anchor/anchor-manager.js.map +1 -1
- package/dist/domains/anchor/services/anchor/anchor-search-service.d.ts +11 -34
- package/dist/domains/anchor/services/anchor/anchor-search-service.d.ts.map +1 -1
- package/dist/domains/anchor/services/anchor/anchor-search-service.js +66 -549
- package/dist/domains/anchor/services/anchor/anchor-search-service.js.map +1 -1
- package/dist/domains/anchor/services/anchor/database-types.d.ts +28 -0
- package/dist/domains/anchor/services/anchor/database-types.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/database-types.js +6 -0
- package/dist/domains/anchor/services/anchor/database-types.js.map +1 -0
- package/dist/domains/anchor/services/anchor/embedding-types.d.ts +20 -0
- package/dist/domains/anchor/services/anchor/embedding-types.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/embedding-types.js +6 -0
- package/dist/domains/anchor/services/anchor/embedding-types.js.map +1 -0
- package/dist/domains/anchor/services/anchor/fallback-search-service.d.ts +36 -0
- package/dist/domains/anchor/services/anchor/fallback-search-service.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/fallback-search-service.js +85 -0
- package/dist/domains/anchor/services/anchor/fallback-search-service.js.map +1 -0
- package/dist/domains/anchor/services/anchor/fallback-strategy.d.ts +25 -0
- package/dist/domains/anchor/services/anchor/fallback-strategy.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/fallback-strategy.js +35 -0
- package/dist/domains/anchor/services/anchor/fallback-strategy.js.map +1 -0
- package/dist/domains/anchor/services/anchor/local-search-service.d.ts +72 -0
- package/dist/domains/anchor/services/anchor/local-search-service.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/local-search-service.js +129 -0
- package/dist/domains/anchor/services/anchor/local-search-service.js.map +1 -0
- package/dist/domains/anchor/services/anchor/n-hop-search-service.d.ts +90 -0
- package/dist/domains/anchor/services/anchor/n-hop-search-service.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/n-hop-search-service.js +385 -0
- package/dist/domains/anchor/services/anchor/n-hop-search-service.js.map +1 -0
- package/dist/domains/anchor/services/anchor/n-hop-search-strategy.d.ts +24 -0
- package/dist/domains/anchor/services/anchor/n-hop-search-strategy.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/n-hop-search-strategy.js +39 -0
- package/dist/domains/anchor/services/anchor/n-hop-search-strategy.js.map +1 -0
- package/dist/domains/anchor/services/anchor/query-filter-service.d.ts +52 -0
- package/dist/domains/anchor/services/anchor/query-filter-service.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/query-filter-service.js +136 -0
- package/dist/domains/anchor/services/anchor/query-filter-service.js.map +1 -0
- package/dist/domains/anchor/services/anchor/query-filter-strategy.d.ts +24 -0
- package/dist/domains/anchor/services/anchor/query-filter-strategy.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/query-filter-strategy.js +34 -0
- package/dist/domains/anchor/services/anchor/query-filter-strategy.js.map +1 -0
- package/dist/domains/anchor/services/anchor/search-strategy-interfaces.d.ts +52 -0
- package/dist/domains/anchor/services/anchor/search-strategy-interfaces.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/search-strategy-interfaces.js +6 -0
- package/dist/domains/anchor/services/anchor/search-strategy-interfaces.js.map +1 -0
- package/dist/domains/anchor/services/anchor/vector-search-engine-types.d.ts +18 -0
- package/dist/domains/anchor/services/anchor/vector-search-engine-types.d.ts.map +1 -0
- package/dist/domains/anchor/services/anchor/vector-search-engine-types.js +11 -0
- package/dist/domains/anchor/services/anchor/vector-search-engine-types.js.map +1 -0
- package/dist/domains/memory/repositories/core-memory-database.interface.d.ts +47 -0
- package/dist/domains/memory/repositories/core-memory-database.interface.d.ts.map +1 -0
- package/dist/domains/memory/repositories/core-memory-database.interface.js +7 -0
- package/dist/domains/memory/repositories/core-memory-database.interface.js.map +1 -0
- package/dist/domains/memory/repositories/core-memory-repository.d.ts +7 -77
- package/dist/domains/memory/repositories/core-memory-repository.d.ts.map +1 -1
- package/dist/domains/memory/repositories/core-memory-repository.interface.d.ts +92 -0
- package/dist/domains/memory/repositories/core-memory-repository.interface.d.ts.map +1 -0
- package/dist/domains/memory/repositories/core-memory-repository.interface.js +6 -0
- package/dist/domains/memory/repositories/core-memory-repository.interface.js.map +1 -0
- package/dist/domains/memory/repositories/core-memory-repository.js +7 -259
- package/dist/domains/memory/repositories/core-memory-repository.js.map +1 -1
- package/dist/domains/memory/services/core-memory-cache-service.d.ts +55 -0
- package/dist/domains/memory/services/core-memory-cache-service.d.ts.map +1 -1
- package/dist/domains/memory/services/core-memory-cache-service.js +110 -8
- package/dist/domains/memory/services/core-memory-cache-service.js.map +1 -1
- package/dist/domains/memory/services/core-memory-service.d.ts +2 -2
- package/dist/domains/memory/services/core-memory-service.d.ts.map +1 -1
- package/dist/domains/memory/services/core-memory-service.js +43 -24
- package/dist/domains/memory/services/core-memory-service.js.map +1 -1
- package/dist/domains/memory/tools/recall-tool.d.ts.map +1 -1
- package/dist/domains/memory/tools/recall-tool.js +2 -2
- package/dist/domains/memory/tools/recall-tool.js.map +1 -1
- package/dist/domains/memory/tools/remember-tool.d.ts.map +1 -1
- package/dist/domains/memory/tools/remember-tool.js +2 -2
- package/dist/domains/memory/tools/remember-tool.js.map +1 -1
- package/dist/domains/monitoring/services/performance-monitor.d.ts +8 -0
- package/dist/domains/monitoring/services/performance-monitor.d.ts.map +1 -1
- package/dist/domains/monitoring/services/performance-monitor.js +16 -0
- package/dist/domains/monitoring/services/performance-monitor.js.map +1 -1
- package/dist/infrastructure/async-optimizer.d.ts.map +1 -1
- package/dist/infrastructure/async-optimizer.js +8 -7
- package/dist/infrastructure/async-optimizer.js.map +1 -1
- package/dist/infrastructure/database/adapters/sqlite-core-memory-adapter.d.ts +31 -0
- package/dist/infrastructure/database/adapters/sqlite-core-memory-adapter.d.ts.map +1 -0
- package/dist/infrastructure/database/adapters/sqlite-core-memory-adapter.js +112 -0
- package/dist/infrastructure/database/adapters/sqlite-core-memory-adapter.js.map +1 -0
- package/dist/infrastructure/database/database/init.d.ts.map +1 -1
- package/dist/infrastructure/database/database/init.js +23 -2
- package/dist/infrastructure/database/database/init.js.map +1 -1
- package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.d.ts +64 -0
- package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.d.ts.map +1 -0
- package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.js +174 -0
- package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.js.map +1 -0
- package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.sql +25 -0
- package/dist/infrastructure/database/database-lock-monitor.d.ts +105 -0
- package/dist/infrastructure/database/database-lock-monitor.d.ts.map +1 -0
- package/dist/infrastructure/database/database-lock-monitor.js +265 -0
- package/dist/infrastructure/database/database-lock-monitor.js.map +1 -0
- package/dist/infrastructure/database/factories/core-memory-repository.factory.d.ts +17 -0
- package/dist/infrastructure/database/factories/core-memory-repository.factory.d.ts.map +1 -0
- package/dist/infrastructure/database/factories/core-memory-repository.factory.js +43 -0
- package/dist/infrastructure/database/factories/core-memory-repository.factory.js.map +1 -0
- package/dist/infrastructure/database/repositories/core-memory-repository-sqlite.impl.d.ts +63 -0
- package/dist/infrastructure/database/repositories/core-memory-repository-sqlite.impl.d.ts.map +1 -0
- package/dist/infrastructure/database/repositories/core-memory-repository-sqlite.impl.js +281 -0
- package/dist/infrastructure/database/repositories/core-memory-repository-sqlite.impl.js.map +1 -0
- package/dist/infrastructure/database/wal-checkpoint-scheduler.d.ts +166 -0
- package/dist/infrastructure/database/wal-checkpoint-scheduler.d.ts.map +1 -0
- package/dist/infrastructure/database/wal-checkpoint-scheduler.js +285 -0
- package/dist/infrastructure/database/wal-checkpoint-scheduler.js.map +1 -0
- package/dist/npm-client/memento-client.d.ts.map +1 -1
- package/dist/npm-client/memento-client.js +5 -4
- package/dist/npm-client/memento-client.js.map +1 -1
- package/dist/npm-client/memory-manager.d.ts.map +1 -1
- package/dist/npm-client/memory-manager.js +4 -3
- package/dist/npm-client/memory-manager.js.map +1 -1
- package/dist/scripts/check-migration-status.d.ts.map +1 -1
- package/dist/scripts/check-migration-status.js +1 -0
- package/dist/scripts/check-migration-status.js.map +1 -1
- package/dist/server/bootstrap.d.ts +4 -0
- package/dist/server/bootstrap.d.ts.map +1 -1
- package/dist/server/bootstrap.js +27 -2
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/server/http-server.d.ts.map +1 -1
- package/dist/server/http-server.js +59 -31
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/index.js +32 -16
- package/dist/server/index.js.map +1 -1
- package/dist/server/simple-mcp-server.d.ts.map +1 -1
- package/dist/server/simple-mcp-server.js +15 -14
- package/dist/server/simple-mcp-server.js.map +1 -1
- package/dist/shared/config/environment.d.ts.map +1 -1
- package/dist/shared/config/environment.js +13 -1
- package/dist/shared/config/environment.js.map +1 -1
- package/dist/shared/config/index.d.ts.map +1 -1
- package/dist/shared/config/index.js +13 -1
- package/dist/shared/config/index.js.map +1 -1
- package/dist/shared/types/index.d.ts +10 -0
- package/dist/shared/types/index.d.ts.map +1 -1
- package/dist/tools/base-tool.d.ts.map +1 -1
- package/dist/tools/base-tool.js +5 -4
- package/dist/tools/base-tool.js.map +1 -1
- package/dist/tools/migrate-embeddings-tool.d.ts.map +1 -1
- package/dist/tools/migrate-embeddings-tool.js +2 -1
- package/dist/tools/migrate-embeddings-tool.js.map +1 -1
- package/dist/tools/tool-registry.d.ts.map +1 -1
- package/dist/tools/tool-registry.js +4 -3
- package/dist/tools/tool-registry.js.map +1 -1
- package/dist/workers/consolidation-score-worker.d.ts.map +1 -1
- package/dist/workers/consolidation-score-worker.js +5 -4
- package/dist/workers/consolidation-score-worker.js.map +1 -1
- package/package.json +1 -1
- package/scripts/check-file-sizes.ts +405 -0
- package/scripts/count-any-types.ts +375 -0
- package/scripts/count-console-logs.ts +451 -0
|
@@ -6,6 +6,13 @@
|
|
|
6
6
|
import { UnifiedEmbeddingService } from '../../../embedding/services/unified-embedding-service.js';
|
|
7
7
|
import { logger } from '../../../../shared/utils/logger.js';
|
|
8
8
|
import { RelationGraph } from '../../../relation/services/relation-graph.js';
|
|
9
|
+
import { NHopSearchService } from './n-hop-search-service.js';
|
|
10
|
+
import { QueryFilterService } from './query-filter-service.js';
|
|
11
|
+
import { FallbackSearchService } from './fallback-search-service.js';
|
|
12
|
+
import { LocalSearchService } from './local-search-service.js';
|
|
13
|
+
import { NHopSearchStrategy } from './n-hop-search-strategy.js';
|
|
14
|
+
import { QueryFilterStrategy } from './query-filter-strategy.js';
|
|
15
|
+
import { FallbackStrategy } from './fallback-strategy.js';
|
|
9
16
|
/**
|
|
10
17
|
* Anchor Search Service 구현
|
|
11
18
|
*/
|
|
@@ -16,11 +23,21 @@ export class AnchorSearchService {
|
|
|
16
23
|
vectorSearchEngine = null;
|
|
17
24
|
queryEmbeddingService = new UnifiedEmbeddingService();
|
|
18
25
|
relationGraph = null;
|
|
26
|
+
// Phase 2.3-2.5: 분리된 서비스들
|
|
27
|
+
nHopSearchService;
|
|
28
|
+
queryFilterService;
|
|
29
|
+
fallbackSearchService;
|
|
30
|
+
// Phase 3.6: LocalSearchService
|
|
31
|
+
localSearchService = null;
|
|
19
32
|
/**
|
|
20
33
|
* 생성자
|
|
21
34
|
*/
|
|
22
35
|
constructor(cacheService) {
|
|
23
36
|
this.cacheService = cacheService;
|
|
37
|
+
// Phase 2.3-2.5: 분리된 서비스들 초기화
|
|
38
|
+
this.nHopSearchService = new NHopSearchService(cacheService);
|
|
39
|
+
this.queryFilterService = new QueryFilterService(cacheService);
|
|
40
|
+
this.fallbackSearchService = new FallbackSearchService();
|
|
24
41
|
logger.info('AnchorSearchService 초기화 완료');
|
|
25
42
|
}
|
|
26
43
|
/**
|
|
@@ -28,6 +45,8 @@ export class AnchorSearchService {
|
|
|
28
45
|
*/
|
|
29
46
|
setRelationGraph(relationGraph) {
|
|
30
47
|
this.relationGraph = relationGraph;
|
|
48
|
+
// Phase 2.3: N-hop 검색 서비스에도 관계 그래프 설정
|
|
49
|
+
this.nHopSearchService.setRelationGraph(relationGraph);
|
|
31
50
|
}
|
|
32
51
|
/**
|
|
33
52
|
* 데이터베이스 설정
|
|
@@ -37,6 +56,17 @@ export class AnchorSearchService {
|
|
|
37
56
|
throw new Error('Database instance is required');
|
|
38
57
|
}
|
|
39
58
|
this.db = db;
|
|
59
|
+
// Phase 2.3-2.5: 분리된 서비스들에도 데이터베이스 설정
|
|
60
|
+
this.nHopSearchService.setDatabase(db);
|
|
61
|
+
this.fallbackSearchService.setDatabase(db);
|
|
62
|
+
// Phase 3.6: LocalSearchService 초기화
|
|
63
|
+
if (!this.localSearchService) {
|
|
64
|
+
const nHopSearchStrategy = new NHopSearchStrategy(this.nHopSearchService);
|
|
65
|
+
const queryFilterStrategy = new QueryFilterStrategy(this.queryFilterService);
|
|
66
|
+
const fallbackStrategy = new FallbackStrategy(this.fallbackSearchService);
|
|
67
|
+
this.localSearchService = new LocalSearchService(this.cacheService, nHopSearchStrategy, queryFilterStrategy, fallbackStrategy);
|
|
68
|
+
}
|
|
69
|
+
this.localSearchService.setDatabase(db);
|
|
40
70
|
}
|
|
41
71
|
/**
|
|
42
72
|
* 하이브리드 검색 엔진 설정
|
|
@@ -46,6 +76,8 @@ export class AnchorSearchService {
|
|
|
46
76
|
throw new Error('HybridSearchEngine is required');
|
|
47
77
|
}
|
|
48
78
|
this.hybridSearchEngine = hybridSearchEngine;
|
|
79
|
+
// Phase 2.5: Fallback 검색 서비스에도 하이브리드 검색 엔진 설정
|
|
80
|
+
this.fallbackSearchService.setHybridSearchEngine(hybridSearchEngine);
|
|
49
81
|
}
|
|
50
82
|
/**
|
|
51
83
|
* 벡터 검색 엔진 설정
|
|
@@ -59,15 +91,21 @@ export class AnchorSearchService {
|
|
|
59
91
|
if (this.db) {
|
|
60
92
|
this.vectorSearchEngine.initialize(this.db);
|
|
61
93
|
}
|
|
94
|
+
// Phase 2.3: N-hop 검색 서비스에도 벡터 검색 엔진 설정
|
|
95
|
+
this.nHopSearchService.setVectorSearchEngine(vectorSearchEngine);
|
|
62
96
|
}
|
|
63
97
|
/**
|
|
64
98
|
* 국소 검색
|
|
65
99
|
* 앵커 메모리를 기준으로 N-hop 제한 검색 수행
|
|
100
|
+
* Phase 3.7: LocalSearchService를 사용하도록 리팩토링
|
|
66
101
|
*/
|
|
67
102
|
async searchLocal(agentId, slot, query, hopLimit, options, anchorMemoryId, anchorEmbedding, startTime) {
|
|
68
103
|
if (!this.db) {
|
|
69
104
|
throw new Error('Database is not set. Call setDatabase() first.');
|
|
70
105
|
}
|
|
106
|
+
if (!this.localSearchService) {
|
|
107
|
+
throw new Error('LocalSearchService is not initialized. Call setDatabase() first.');
|
|
108
|
+
}
|
|
71
109
|
// 슬롯별 설정 가져오기
|
|
72
110
|
const slotConfig = this.getSlotConfig(slot);
|
|
73
111
|
const finalHopLimit = hopLimit ?? slotConfig.hop_limit;
|
|
@@ -80,16 +118,15 @@ export class AnchorSearchService {
|
|
|
80
118
|
if (!this.vectorSearchEngine) {
|
|
81
119
|
throw new Error('VectorSearchEngine is not set. Call setVectorSearchEngine() first.');
|
|
82
120
|
}
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
);
|
|
87
|
-
//
|
|
88
|
-
|
|
121
|
+
// Phase 3.6: LocalSearchService를 사용하여 파이프라인 실행
|
|
122
|
+
// 1. N-hop 검색 수행
|
|
123
|
+
const allHopResults = await this.localSearchService.performNHopSearch(anchorEmbedding.embedding, anchorEmbedding.provider, anchorMemoryId, vectorThreshold, finalHopLimit, limit * 2, // 더 많이 가져와서 필터링 후 최종 limit 적용
|
|
124
|
+
useRelations);
|
|
125
|
+
// 2. 쿼리 필터링 적용
|
|
126
|
+
const filteredResults = await this.localSearchService.applyQueryFilter(query, allHopResults, anchorEmbedding.provider);
|
|
127
|
+
// 자동 앵커 이동을 위한 쿼리 임베딩 생성 (선택적, 비동기)
|
|
89
128
|
let queryEmbeddingForReanchor;
|
|
90
129
|
if (query && query.trim().length > 0) {
|
|
91
|
-
filteredResults = await this.filterByQuery(query, allHopResults, anchorEmbedding.provider);
|
|
92
|
-
// 자동 앵커 이동을 위한 쿼리 임베딩 생성 (선택적, 비동기)
|
|
93
130
|
try {
|
|
94
131
|
const queryEmbeddingResult = await this.queryEmbeddingService.generateEmbedding(query);
|
|
95
132
|
if (queryEmbeddingResult && queryEmbeddingResult.embedding) {
|
|
@@ -103,7 +140,7 @@ export class AnchorSearchService {
|
|
|
103
140
|
});
|
|
104
141
|
}
|
|
105
142
|
}
|
|
106
|
-
// 결과 포맷팅
|
|
143
|
+
// 3. 결과 포맷팅
|
|
107
144
|
const formattedResults = filteredResults.map(result => ({
|
|
108
145
|
id: result.memory_id,
|
|
109
146
|
content: result.content,
|
|
@@ -117,58 +154,14 @@ export class AnchorSearchService {
|
|
|
117
154
|
// 최종 limit 적용
|
|
118
155
|
const localResults = formattedResults.slice(0, limit);
|
|
119
156
|
const localCount = localResults.length;
|
|
120
|
-
// Fallback
|
|
121
|
-
|
|
122
|
-
let finalResults = localResults;
|
|
123
|
-
let totalCount = localCount;
|
|
124
|
-
if (query && query.trim().length > 0 && localCount < minResults) {
|
|
125
|
-
try {
|
|
126
|
-
logger.info('Fallback to global search', {
|
|
127
|
-
localCount,
|
|
128
|
-
minResults
|
|
129
|
-
});
|
|
130
|
-
// Fallback 수행
|
|
131
|
-
const fallbackResult = await this.fallbackToGlobalSearch(query, { ...options, limit: limit - localCount }, // 부족한 만큼만 가져오기
|
|
132
|
-
startTime);
|
|
133
|
-
fallbackUsed = true;
|
|
134
|
-
// Local 결과와 Fallback 결과 병합
|
|
135
|
-
// Local 결과를 우선하고, 중복 제거 (memory_id 기준)
|
|
136
|
-
const localMemoryIds = new Set(localResults.map(r => r.id));
|
|
137
|
-
const fallbackItems = fallbackResult.items
|
|
138
|
-
.filter(item => !localMemoryIds.has(item.id))
|
|
139
|
-
.map(item => ({
|
|
140
|
-
id: item.id,
|
|
141
|
-
content: item.content,
|
|
142
|
-
type: item.type,
|
|
143
|
-
similarity: item.similarity ?? 0,
|
|
144
|
-
hop_distance: item.hop_distance ?? 999, // fallback 결과는 hop_distance가 없으므로 큰 값으로 설정
|
|
145
|
-
importance: item.importance ?? 0.5,
|
|
146
|
-
created_at: item.created_at ?? new Date().toISOString(),
|
|
147
|
-
tags: item.tags ?? undefined
|
|
148
|
-
}));
|
|
149
|
-
// Local 결과 + Fallback 결과 (중복 제거된 것만)
|
|
150
|
-
finalResults = [...localResults, ...fallbackItems].slice(0, limit);
|
|
151
|
-
totalCount = finalResults.length;
|
|
152
|
-
logger.info('Fallback completed', {
|
|
153
|
-
localCount,
|
|
154
|
-
fallbackCount: fallbackItems.length,
|
|
155
|
-
totalCount
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
logger.error('Fallback failed', {
|
|
160
|
-
error: error instanceof Error ? error.message : String(error)
|
|
161
|
-
});
|
|
162
|
-
// Fallback 실패 시 local 결과만 반환
|
|
163
|
-
fallbackUsed = false;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
157
|
+
// 4. Fallback 처리
|
|
158
|
+
const fallbackResult = await this.localSearchService.handleFallback(query, localResults, minResults, options, startTime);
|
|
166
159
|
const queryTime = Date.now() - startTime;
|
|
167
160
|
return {
|
|
168
|
-
items:
|
|
169
|
-
total_count: totalCount,
|
|
161
|
+
items: fallbackResult.items,
|
|
162
|
+
total_count: fallbackResult.totalCount,
|
|
170
163
|
local_results_count: localCount,
|
|
171
|
-
fallback_used: fallbackUsed,
|
|
164
|
+
fallback_used: fallbackResult.fallbackUsed,
|
|
172
165
|
query_time: queryTime,
|
|
173
166
|
anchor_info: {
|
|
174
167
|
agent_id: agentId,
|
|
@@ -178,493 +171,19 @@ export class AnchorSearchService {
|
|
|
178
171
|
};
|
|
179
172
|
}
|
|
180
173
|
/**
|
|
181
|
-
*
|
|
182
|
-
*/
|
|
183
|
-
async searchOneHop(anchorEmbedding, provider, anchorMemoryId, threshold, limit) {
|
|
184
|
-
if (!this.vectorSearchEngine || !this.db) {
|
|
185
|
-
throw new Error('VectorSearchEngine or Database is not set.');
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
// VectorSearchEngine 초기화 확인
|
|
189
|
-
if (typeof this.vectorSearchEngine.initialize === 'function') {
|
|
190
|
-
this.vectorSearchEngine.initialize(this.db);
|
|
191
|
-
}
|
|
192
|
-
// 벡터 검색 실행 (임계값은 낮게 설정하고 나중에 필터링)
|
|
193
|
-
const searchResults = await this.vectorSearchEngine.search(anchorEmbedding, {
|
|
194
|
-
limit: limit + 1, // 자기 자신 제외를 위해 +1
|
|
195
|
-
threshold: 0.0, // 임계값은 나중에 필터링에서 적용
|
|
196
|
-
includeContent: true,
|
|
197
|
-
includeMetadata: true
|
|
198
|
-
}, provider);
|
|
199
|
-
// 결과 필터링: 앵커 메모리 제외, 유사도 임계값 이상만 반환
|
|
200
|
-
const filteredResults = searchResults
|
|
201
|
-
.filter(result => {
|
|
202
|
-
// 앵커 메모리 제외
|
|
203
|
-
if (result.memory_id === anchorMemoryId) {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
// 유사도 임계값 이상만 반환
|
|
207
|
-
return result.similarity >= threshold;
|
|
208
|
-
})
|
|
209
|
-
.slice(0, limit); // 최종 limit 적용
|
|
210
|
-
return filteredResults;
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
logger.error('1-hop search failed', {
|
|
214
|
-
error: error instanceof Error ? error.message : String(error)
|
|
215
|
-
});
|
|
216
|
-
throw new Error(`Failed to perform 1-hop search: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* N-hop 검색: 앵커를 기준으로 최대 N-hop까지 확장 검색
|
|
221
|
-
*/
|
|
222
|
-
async searchNHop(anchorEmbedding, provider, anchorMemoryId, threshold, maxHops, limit, useRelations = true) {
|
|
223
|
-
if (!this.vectorSearchEngine || !this.db) {
|
|
224
|
-
throw new Error('VectorSearchEngine or Database is not set.');
|
|
225
|
-
}
|
|
226
|
-
// VectorSearchEngine 초기화 확인
|
|
227
|
-
if (typeof this.vectorSearchEngine.initialize === 'function') {
|
|
228
|
-
this.vectorSearchEngine.initialize(this.db);
|
|
229
|
-
}
|
|
230
|
-
// 이미 발견된 메모리 ID 추적 (중복 방지)
|
|
231
|
-
const discoveredMemoryIds = new Set([anchorMemoryId]);
|
|
232
|
-
// 각 hop 레벨의 결과를 저장
|
|
233
|
-
const allResults = [];
|
|
234
|
-
// 현재 hop 레벨의 메모리들 (임베딩 포함)
|
|
235
|
-
// 1-hop: 앵커 임베딩을 사용
|
|
236
|
-
let currentHopMemories = [
|
|
237
|
-
{ memory_id: anchorMemoryId, embedding: anchorEmbedding }
|
|
238
|
-
];
|
|
239
|
-
// 각 hop 레벨별로 검색 수행
|
|
240
|
-
for (let hop = 1; hop <= maxHops; hop++) {
|
|
241
|
-
const nextHopMemories = [];
|
|
242
|
-
const hopResults = [];
|
|
243
|
-
// 현재 hop의 각 메모리에 대해 검색 수행
|
|
244
|
-
for (const currentMemory of currentHopMemories) {
|
|
245
|
-
try {
|
|
246
|
-
// 관계 그래프 또는 memory_link를 활용한 직접 연결된 메모리 조회
|
|
247
|
-
// use_relations가 false이면 관계 그래프를 사용하지 않음
|
|
248
|
-
const linkedMemories = useRelations
|
|
249
|
-
? await this.getLinkedMemories(currentMemory.memory_id)
|
|
250
|
-
: [];
|
|
251
|
-
// 벡터 검색 실행
|
|
252
|
-
const vectorSearchResults = await this.vectorSearchEngine.search(currentMemory.embedding, {
|
|
253
|
-
limit: Math.ceil(limit / maxHops) + 10, // 각 hop당 충분한 결과 가져오기
|
|
254
|
-
threshold: 0.0, // 임계값은 나중에 필터링에서 적용
|
|
255
|
-
includeContent: true,
|
|
256
|
-
includeMetadata: true
|
|
257
|
-
}, provider);
|
|
258
|
-
// memory_link 결과와 벡터 검색 결과를 병합
|
|
259
|
-
const allCandidates = new Map();
|
|
260
|
-
// memory_link 결과 추가 (우선순위 높음)
|
|
261
|
-
for (const linked of linkedMemories) {
|
|
262
|
-
if (!discoveredMemoryIds.has(linked.memory_id)) {
|
|
263
|
-
allCandidates.set(linked.memory_id, {
|
|
264
|
-
...linked,
|
|
265
|
-
isLinked: true
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
// 벡터 검색 결과 추가
|
|
270
|
-
const relaxedThreshold = threshold * 0.5;
|
|
271
|
-
for (const result of vectorSearchResults) {
|
|
272
|
-
if (!allCandidates.has(result.memory_id) && !discoveredMemoryIds.has(result.memory_id)) {
|
|
273
|
-
if (result.similarity >= relaxedThreshold) {
|
|
274
|
-
allCandidates.set(result.memory_id, {
|
|
275
|
-
...result,
|
|
276
|
-
isLinked: false
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
else if (allCandidates.has(result.memory_id)) {
|
|
281
|
-
// memory_link로 이미 추가된 경우, 유사도 정보 업데이트
|
|
282
|
-
const existing = allCandidates.get(result.memory_id);
|
|
283
|
-
existing.similarity = Math.max(existing.similarity, result.similarity);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
// 결과 필터링 및 추가
|
|
287
|
-
for (const [memoryId, candidate] of allCandidates.entries()) {
|
|
288
|
-
if (discoveredMemoryIds.has(memoryId)) {
|
|
289
|
-
continue;
|
|
290
|
-
}
|
|
291
|
-
const effectiveThreshold = candidate.isLinked
|
|
292
|
-
? threshold * 0.8
|
|
293
|
-
: threshold;
|
|
294
|
-
if (candidate.similarity < effectiveThreshold) {
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
discoveredMemoryIds.add(memoryId);
|
|
298
|
-
hopResults.push({
|
|
299
|
-
memory_id: candidate.memory_id,
|
|
300
|
-
content: candidate.content,
|
|
301
|
-
type: candidate.type,
|
|
302
|
-
similarity: candidate.similarity,
|
|
303
|
-
importance: candidate.importance,
|
|
304
|
-
created_at: candidate.created_at,
|
|
305
|
-
tags: candidate.tags,
|
|
306
|
-
hasRelation: candidate.isLinked // 관계가 있는 기억 표시
|
|
307
|
-
});
|
|
308
|
-
// 다음 hop을 위한 임베딩 조회
|
|
309
|
-
if (hop < maxHops) {
|
|
310
|
-
try {
|
|
311
|
-
const nextEmbedding = await this.cacheService.getAnchorEmbedding(candidate.memory_id);
|
|
312
|
-
if (nextEmbedding && nextEmbedding.embedding) {
|
|
313
|
-
nextHopMemories.push({
|
|
314
|
-
memory_id: candidate.memory_id,
|
|
315
|
-
embedding: nextEmbedding.embedding
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
catch (error) {
|
|
320
|
-
// 임베딩 조회 실패 시 다음 hop에서 제외
|
|
321
|
-
logger.debug('Skipping memory for next hop (no embedding)', {
|
|
322
|
-
memoryId: candidate.memory_id,
|
|
323
|
-
error: error instanceof Error ? error.message : String(error)
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
catch (error) {
|
|
330
|
-
logger.error('Hop search failed', {
|
|
331
|
-
hop,
|
|
332
|
-
memoryId: currentMemory.memory_id,
|
|
333
|
-
error: error instanceof Error ? error.message : String(error)
|
|
334
|
-
});
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
// 현재 hop의 결과를 전체 결과에 추가
|
|
339
|
-
for (const result of hopResults) {
|
|
340
|
-
allResults.push({
|
|
341
|
-
...result,
|
|
342
|
-
hop_distance: hop,
|
|
343
|
-
hasRelation: result.hasRelation ?? false
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
// limit에 도달했으면 중단
|
|
347
|
-
if (allResults.length >= limit) {
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
// 다음 hop을 위한 메모리가 없으면 중단
|
|
351
|
-
if (nextHopMemories.length === 0) {
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
// 다음 hop을 위한 메모리로 업데이트
|
|
355
|
-
currentHopMemories = nextHopMemories;
|
|
356
|
-
}
|
|
357
|
-
// 랭킹 점수 계산 및 적용 (관계 그래프 가중치 포함)
|
|
358
|
-
const rankedResults = await Promise.all(allResults.map(async (result) => {
|
|
359
|
-
// 관계 가중치 계산 (관계 그래프가 있고 use_relations가 true인 경우)
|
|
360
|
-
let relationWeight = 0;
|
|
361
|
-
let hasRelation = result.hasRelation ?? false;
|
|
362
|
-
if (useRelations && this.relationGraph) {
|
|
363
|
-
try {
|
|
364
|
-
const relations = await this.relationGraph.getRelations(result.memory_id, {
|
|
365
|
-
direction: 'both',
|
|
366
|
-
minConfidence: 0.5
|
|
367
|
-
});
|
|
368
|
-
if (relations.length > 0) {
|
|
369
|
-
hasRelation = true; // 관계가 있음을 표시
|
|
370
|
-
// 관계 가중치 계산 (간단한 평균)
|
|
371
|
-
const avgConfidence = relations.reduce((sum, r) => sum + r.confidence, 0) / relations.length;
|
|
372
|
-
const avgBoost = relations.reduce((sum, r) => sum + this.getRelationTypeBoost(r.relation_type), 0) / relations.length;
|
|
373
|
-
relationWeight = Math.min(1.0, avgConfidence * avgBoost);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
catch (error) {
|
|
377
|
-
// 관계 조회 실패는 무시
|
|
378
|
-
logger.debug('Relation weight calculation failed', {
|
|
379
|
-
memoryId: result.memory_id,
|
|
380
|
-
error: error instanceof Error ? error.message : String(error)
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
const rankingScore = this.calculateRankingScore(result.similarity, result.hop_distance, result.importance, relationWeight, hasRelation // 관계가 있는 기억에 우선순위 부여
|
|
385
|
-
);
|
|
386
|
-
return {
|
|
387
|
-
...result,
|
|
388
|
-
similarity: rankingScore,
|
|
389
|
-
hasRelation
|
|
390
|
-
};
|
|
391
|
-
}));
|
|
392
|
-
// 랭킹 점수 기준으로 정렬 (관계가 있는 기억 우선)
|
|
393
|
-
rankedResults.sort((a, b) => {
|
|
394
|
-
// 관계가 있는 기억에 우선순위 부여
|
|
395
|
-
if (a.hasRelation && !b.hasRelation) {
|
|
396
|
-
return -1; // a가 우선
|
|
397
|
-
}
|
|
398
|
-
if (!a.hasRelation && b.hasRelation) {
|
|
399
|
-
return 1; // b가 우선
|
|
400
|
-
}
|
|
401
|
-
// 둘 다 관계가 있거나 둘 다 없는 경우, similarity 기준 정렬
|
|
402
|
-
if (Math.abs(a.similarity - b.similarity) < 0.001) {
|
|
403
|
-
return a.hop_distance - b.hop_distance;
|
|
404
|
-
}
|
|
405
|
-
return b.similarity - a.similarity;
|
|
406
|
-
});
|
|
407
|
-
// 최종 limit 적용
|
|
408
|
-
return rankedResults.slice(0, limit);
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* 쿼리 기반 필터링
|
|
412
|
-
*/
|
|
413
|
-
async filterByQuery(query, results, provider) {
|
|
414
|
-
if (results.length === 0) {
|
|
415
|
-
return results;
|
|
416
|
-
}
|
|
417
|
-
try {
|
|
418
|
-
// 쿼리 임베딩 생성
|
|
419
|
-
const queryEmbeddingResult = await this.queryEmbeddingService.generateEmbedding(query);
|
|
420
|
-
if (!queryEmbeddingResult || !queryEmbeddingResult.embedding) {
|
|
421
|
-
logger.warn('Query embedding generation failed, skipping filter');
|
|
422
|
-
return results;
|
|
423
|
-
}
|
|
424
|
-
const queryEmbedding = queryEmbeddingResult.embedding;
|
|
425
|
-
// 각 결과 메모리의 임베딩 조회 및 쿼리 유사도 계산
|
|
426
|
-
const resultsWithQuerySimilarity = await Promise.all(results.map(async (result) => {
|
|
427
|
-
try {
|
|
428
|
-
const memoryEmbedding = await this.cacheService.getAnchorEmbedding(result.memory_id);
|
|
429
|
-
if (!memoryEmbedding || !memoryEmbedding.embedding) {
|
|
430
|
-
return {
|
|
431
|
-
...result,
|
|
432
|
-
query_similarity: 0,
|
|
433
|
-
combined_similarity: result.similarity * 0.5
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
// 쿼리 임베딩과 메모리 임베딩 간 유사도 계산
|
|
437
|
-
let querySim = 0;
|
|
438
|
-
if (queryEmbedding.length === memoryEmbedding.embedding.length) {
|
|
439
|
-
querySim = this.cosineSimilarity(queryEmbedding, memoryEmbedding.embedding);
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
// 차원이 다르면 텍스트 기반 간단한 매칭
|
|
443
|
-
const queryLower = query.toLowerCase();
|
|
444
|
-
const contentLower = result.content.toLowerCase();
|
|
445
|
-
const queryWords = queryLower.split(/\s+/);
|
|
446
|
-
const matchCount = queryWords.filter(word => contentLower.includes(word)).length;
|
|
447
|
-
querySim = matchCount / Math.max(queryWords.length, 1);
|
|
448
|
-
}
|
|
449
|
-
const baseRankingScore = this.calculateRankingScore(result.similarity, result.hop_distance, result.importance, 0 // query filtering에서는 관계 가중치 미사용 (이미 searchNHop에서 적용됨)
|
|
450
|
-
);
|
|
451
|
-
const combinedSimilarity = baseRankingScore * 0.6 + querySim * 0.4;
|
|
452
|
-
return {
|
|
453
|
-
...result,
|
|
454
|
-
query_similarity: querySim,
|
|
455
|
-
combined_similarity: combinedSimilarity
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
catch (error) {
|
|
459
|
-
logger.error('Query filtering failed for memory', {
|
|
460
|
-
memoryId: result.memory_id,
|
|
461
|
-
error: error instanceof Error ? error.message : String(error)
|
|
462
|
-
});
|
|
463
|
-
return {
|
|
464
|
-
...result,
|
|
465
|
-
query_similarity: 0,
|
|
466
|
-
combined_similarity: result.similarity * 0.5
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
}));
|
|
470
|
-
// 쿼리 유사도 임계값 적용
|
|
471
|
-
const queryThreshold = 0.3;
|
|
472
|
-
const filtered = resultsWithQuerySimilarity.filter(r => r.query_similarity >= queryThreshold || r.combined_similarity >= 0.5);
|
|
473
|
-
// 결합 유사도 기준으로 재정렬
|
|
474
|
-
filtered.sort((a, b) => {
|
|
475
|
-
if (Math.abs(a.combined_similarity - b.combined_similarity) < 0.001) {
|
|
476
|
-
return a.hop_distance - b.hop_distance;
|
|
477
|
-
}
|
|
478
|
-
return b.combined_similarity - a.combined_similarity;
|
|
479
|
-
});
|
|
480
|
-
// 원본 similarity를 combined_similarity로 업데이트하여 반환
|
|
481
|
-
return filtered.map(r => ({
|
|
482
|
-
...r,
|
|
483
|
-
similarity: r.combined_similarity
|
|
484
|
-
}));
|
|
485
|
-
}
|
|
486
|
-
catch (error) {
|
|
487
|
-
logger.error('Query-based filtering failed', {
|
|
488
|
-
error: error instanceof Error ? error.message : String(error)
|
|
489
|
-
});
|
|
490
|
-
return results;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* 전역 검색으로 Fallback
|
|
495
|
-
*/
|
|
496
|
-
async fallbackToGlobalSearch(query, options, startTime) {
|
|
497
|
-
if (!this.hybridSearchEngine) {
|
|
498
|
-
throw new Error('HybridSearchEngine is not set. Call setHybridSearchEngine() first.');
|
|
499
|
-
}
|
|
500
|
-
if (!this.db) {
|
|
501
|
-
throw new Error('Database is not set.');
|
|
502
|
-
}
|
|
503
|
-
const limit = options?.limit ?? 10;
|
|
504
|
-
const fallbackStartTime = Date.now();
|
|
505
|
-
try {
|
|
506
|
-
// HybridSearchEngine을 사용한 전역 검색
|
|
507
|
-
const globalSearchResult = await this.hybridSearchEngine.search(this.db, {
|
|
508
|
-
query: query,
|
|
509
|
-
limit: limit,
|
|
510
|
-
vectorWeight: options?.vector_weight,
|
|
511
|
-
textWeight: options?.text_weight
|
|
512
|
-
});
|
|
513
|
-
// HybridSearchResult를 SearchResult 형식으로 변환
|
|
514
|
-
const convertedItems = globalSearchResult.items.map(item => ({
|
|
515
|
-
id: item.id,
|
|
516
|
-
content: item.content,
|
|
517
|
-
type: item.type,
|
|
518
|
-
similarity: item.finalScore,
|
|
519
|
-
importance: item.importance,
|
|
520
|
-
created_at: item.created_at,
|
|
521
|
-
tags: item.tags,
|
|
522
|
-
hop_distance: undefined
|
|
523
|
-
}));
|
|
524
|
-
const queryTime = startTime ? Date.now() - startTime : Date.now() - fallbackStartTime;
|
|
525
|
-
return {
|
|
526
|
-
items: convertedItems,
|
|
527
|
-
total_count: convertedItems.length,
|
|
528
|
-
local_results_count: 0,
|
|
529
|
-
fallback_used: true,
|
|
530
|
-
query_time: queryTime
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
catch (error) {
|
|
534
|
-
logger.error('Global search fallback failed', {
|
|
535
|
-
error: error instanceof Error ? error.message : String(error)
|
|
536
|
-
});
|
|
537
|
-
const queryTime = startTime ? Date.now() - startTime : 0;
|
|
538
|
-
return {
|
|
539
|
-
items: [],
|
|
540
|
-
total_count: 0,
|
|
541
|
-
local_results_count: 0,
|
|
542
|
-
fallback_used: true,
|
|
543
|
-
query_time: queryTime
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* memory_link 테이블을 활용한 직접 연결된 메모리 조회
|
|
549
|
-
*/
|
|
550
|
-
/**
|
|
551
|
-
* 연결된 메모리 조회
|
|
552
|
-
* 관계 그래프가 있으면 우선 사용, 없으면 memory_link 사용
|
|
553
|
-
*/
|
|
554
|
-
async getLinkedMemories(memoryId) {
|
|
555
|
-
if (!this.db) {
|
|
556
|
-
return [];
|
|
557
|
-
}
|
|
558
|
-
try {
|
|
559
|
-
// 관계 그래프가 있으면 우선 사용
|
|
560
|
-
if (this.relationGraph) {
|
|
561
|
-
const relations = await this.relationGraph.getRelations(memoryId, {
|
|
562
|
-
direction: 'outgoing',
|
|
563
|
-
minConfidence: 0.5
|
|
564
|
-
});
|
|
565
|
-
if (relations.length > 0) {
|
|
566
|
-
// 관계를 메모리 정보와 결합
|
|
567
|
-
const memoryIds = relations.map(r => r.target_id);
|
|
568
|
-
const placeholders = memoryIds.map(() => '?').join(',');
|
|
569
|
-
const memoryRecords = this.db.prepare(`
|
|
570
|
-
SELECT id, content, type, importance, created_at, tags
|
|
571
|
-
FROM memory_item
|
|
572
|
-
WHERE id IN (${placeholders})
|
|
573
|
-
`).all(...memoryIds);
|
|
574
|
-
// 관계 정보와 메모리 정보 결합
|
|
575
|
-
const memoryMap = new Map(memoryRecords.map(m => [m.id, m]));
|
|
576
|
-
return relations.map(relation => {
|
|
577
|
-
const memory = memoryMap.get(relation.target_id);
|
|
578
|
-
if (!memory) {
|
|
579
|
-
return null;
|
|
580
|
-
}
|
|
581
|
-
// 관계 confidence를 similarity로 사용 (관계가 있으면 높은 유사도)
|
|
582
|
-
// 관계 유형별 부스트 적용
|
|
583
|
-
const typeBoost = this.getRelationTypeBoost(relation.relation_type);
|
|
584
|
-
const similarity = Math.min(1.0, relation.confidence * typeBoost);
|
|
585
|
-
return {
|
|
586
|
-
memory_id: memory.id,
|
|
587
|
-
content: memory.content,
|
|
588
|
-
type: memory.type,
|
|
589
|
-
similarity,
|
|
590
|
-
importance: memory.importance,
|
|
591
|
-
created_at: memory.created_at,
|
|
592
|
-
tags: memory.tags ? JSON.parse(memory.tags) : undefined
|
|
593
|
-
};
|
|
594
|
-
}).filter((item) => item !== null);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
// 관계 그래프가 없거나 관계가 없으면 memory_link 사용 (하위 호환성)
|
|
598
|
-
const linkedRecords = this.db.prepare(`
|
|
599
|
-
SELECT
|
|
600
|
-
ml.target_id as memory_id,
|
|
601
|
-
mi.content,
|
|
602
|
-
mi.type,
|
|
603
|
-
mi.importance,
|
|
604
|
-
mi.created_at,
|
|
605
|
-
mi.tags,
|
|
606
|
-
ml.relation_type
|
|
607
|
-
FROM memory_link ml
|
|
608
|
-
JOIN memory_item mi ON mi.id = ml.target_id
|
|
609
|
-
WHERE ml.source_id = ?
|
|
610
|
-
ORDER BY ml.created_at DESC
|
|
611
|
-
`).all(memoryId);
|
|
612
|
-
return linkedRecords.map(record => ({
|
|
613
|
-
memory_id: record.memory_id,
|
|
614
|
-
content: record.content,
|
|
615
|
-
type: record.type,
|
|
616
|
-
similarity: 0.9, // memory_link는 기본 유사도 0.9
|
|
617
|
-
importance: record.importance,
|
|
618
|
-
created_at: record.created_at,
|
|
619
|
-
tags: record.tags ? (typeof record.tags === 'string' ? JSON.parse(record.tags) : record.tags) : undefined
|
|
620
|
-
}));
|
|
621
|
-
}
|
|
622
|
-
catch (error) {
|
|
623
|
-
logger.error('memory_link retrieval failed', {
|
|
624
|
-
memoryId,
|
|
625
|
-
error: error instanceof Error ? error.message : String(error)
|
|
626
|
-
});
|
|
627
|
-
return [];
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
/**
|
|
631
|
-
* 관계 유형별 부스트 가중치 반환
|
|
174
|
+
* 슬롯별 설정 조회
|
|
632
175
|
*/
|
|
633
|
-
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
'CONTRASTS_WITH': 0.9,
|
|
639
|
-
'REFERENCES': 0.8,
|
|
640
|
-
'BELONGS_TO': 1.0
|
|
176
|
+
getSlotConfig(slot) {
|
|
177
|
+
const slotConfig = {
|
|
178
|
+
A: { hop_limit: 1, vector_threshold: 0.8 },
|
|
179
|
+
B: { hop_limit: 2, vector_threshold: 0.6 },
|
|
180
|
+
C: { hop_limit: 3, vector_threshold: 0.4 }
|
|
641
181
|
};
|
|
642
|
-
return
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* 검색 결과 랭킹 점수 계산
|
|
646
|
-
* 관계 그래프와 벡터 유사도를 결합한 하이브리드 hop 점수 계산
|
|
647
|
-
* 관계가 있는 기억에 우선순위 부여
|
|
648
|
-
*/
|
|
649
|
-
calculateRankingScore(similarity, hopDistance, importance = 0.5, relationWeight = 0, hasRelation = false) {
|
|
650
|
-
const hopDecayFactor = 1.0 / (1.0 + (hopDistance - 1) * 0.3);
|
|
651
|
-
const anchorProximityBoost = hopDistance === 1 ? 1.2 : 1.0;
|
|
652
|
-
const importanceWeight = 0.1;
|
|
653
|
-
const importanceBoost = 1.0 + (importance - 0.5) * importanceWeight;
|
|
654
|
-
// 관계 가중치가 있으면 벡터 유사도와 결합
|
|
655
|
-
let combinedSimilarity = similarity;
|
|
656
|
-
if (relationWeight > 0) {
|
|
657
|
-
// 관계 가중치와 벡터 유사도를 가중 평균으로 결합
|
|
658
|
-
// 관계 가중치: 30%, 벡터 유사도: 70%
|
|
659
|
-
combinedSimilarity = similarity * 0.7 + relationWeight * 0.3;
|
|
660
|
-
}
|
|
661
|
-
// 관계가 있는 기억에 우선순위 부스트 적용
|
|
662
|
-
const relationPriorityBoost = hasRelation ? 1.15 : 1.0; // 15% 부스트
|
|
663
|
-
const rankingScore = Math.min(1.0, combinedSimilarity * hopDecayFactor * anchorProximityBoost * importanceBoost * relationPriorityBoost);
|
|
664
|
-
return rankingScore;
|
|
182
|
+
return slotConfig[slot];
|
|
665
183
|
}
|
|
666
184
|
/**
|
|
667
185
|
* 코사인 유사도 계산
|
|
186
|
+
* Phase 2.7.3: calculateReanchorScore에서 사용하므로 유지
|
|
668
187
|
*/
|
|
669
188
|
cosineSimilarity(a, b) {
|
|
670
189
|
if (a.length !== b.length) {
|
|
@@ -684,15 +203,11 @@ export class AnchorSearchService {
|
|
|
684
203
|
return magnitude === 0 ? 0 : dotProduct / magnitude;
|
|
685
204
|
}
|
|
686
205
|
/**
|
|
687
|
-
*
|
|
206
|
+
* 전역 검색으로 Fallback
|
|
207
|
+
* Phase 2.5: FallbackSearchService 위임
|
|
688
208
|
*/
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
A: { hop_limit: 1, vector_threshold: 0.8 },
|
|
692
|
-
B: { hop_limit: 2, vector_threshold: 0.6 },
|
|
693
|
-
C: { hop_limit: 3, vector_threshold: 0.4 }
|
|
694
|
-
};
|
|
695
|
-
return slotConfig[slot];
|
|
209
|
+
async fallbackToGlobalSearch(query, options, startTime) {
|
|
210
|
+
return this.fallbackSearchService.fallbackToGlobalSearch(query, options, startTime);
|
|
696
211
|
}
|
|
697
212
|
/**
|
|
698
213
|
* 자동 앵커 이동 점수 계산
|
|
@@ -767,7 +282,9 @@ export class AnchorSearchService {
|
|
|
767
282
|
}
|
|
768
283
|
try {
|
|
769
284
|
const slotConfig = this.getSlotConfig(slot);
|
|
770
|
-
|
|
285
|
+
// Phase 2.3: N-hop 검색 서비스 사용
|
|
286
|
+
const nearbyMemories = await this.nHopSearchService.searchNHop(anchorEmbedding.embedding, anchorEmbedding.provider, anchorMemoryId, slotConfig.vector_threshold * 0.8, slotConfig.hop_limit, 20, true // useRelations 기본값
|
|
287
|
+
);
|
|
771
288
|
const candidates = [];
|
|
772
289
|
for (const memory of nearbyMemories) {
|
|
773
290
|
const score = await this.calculateReanchorScore(memory.memory_id, queryEmbedding, anchorEmbedding.embedding);
|