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.
Files changed (163) hide show
  1. package/dist/client/index.d.ts.map +1 -1
  2. package/dist/client/index.js +4 -3
  3. package/dist/client/index.js.map +1 -1
  4. package/dist/domains/anchor/services/anchor/anchor-interfaces.d.ts +2 -4
  5. package/dist/domains/anchor/services/anchor/anchor-interfaces.d.ts.map +1 -1
  6. package/dist/domains/anchor/services/anchor/anchor-interfaces.js.map +1 -1
  7. package/dist/domains/anchor/services/anchor/anchor-manager.d.ts +2 -2
  8. package/dist/domains/anchor/services/anchor/anchor-manager.d.ts.map +1 -1
  9. package/dist/domains/anchor/services/anchor/anchor-manager.js.map +1 -1
  10. package/dist/domains/anchor/services/anchor/anchor-search-service.d.ts +11 -34
  11. package/dist/domains/anchor/services/anchor/anchor-search-service.d.ts.map +1 -1
  12. package/dist/domains/anchor/services/anchor/anchor-search-service.js +66 -549
  13. package/dist/domains/anchor/services/anchor/anchor-search-service.js.map +1 -1
  14. package/dist/domains/anchor/services/anchor/database-types.d.ts +28 -0
  15. package/dist/domains/anchor/services/anchor/database-types.d.ts.map +1 -0
  16. package/dist/domains/anchor/services/anchor/database-types.js +6 -0
  17. package/dist/domains/anchor/services/anchor/database-types.js.map +1 -0
  18. package/dist/domains/anchor/services/anchor/embedding-types.d.ts +20 -0
  19. package/dist/domains/anchor/services/anchor/embedding-types.d.ts.map +1 -0
  20. package/dist/domains/anchor/services/anchor/embedding-types.js +6 -0
  21. package/dist/domains/anchor/services/anchor/embedding-types.js.map +1 -0
  22. package/dist/domains/anchor/services/anchor/fallback-search-service.d.ts +36 -0
  23. package/dist/domains/anchor/services/anchor/fallback-search-service.d.ts.map +1 -0
  24. package/dist/domains/anchor/services/anchor/fallback-search-service.js +85 -0
  25. package/dist/domains/anchor/services/anchor/fallback-search-service.js.map +1 -0
  26. package/dist/domains/anchor/services/anchor/fallback-strategy.d.ts +25 -0
  27. package/dist/domains/anchor/services/anchor/fallback-strategy.d.ts.map +1 -0
  28. package/dist/domains/anchor/services/anchor/fallback-strategy.js +35 -0
  29. package/dist/domains/anchor/services/anchor/fallback-strategy.js.map +1 -0
  30. package/dist/domains/anchor/services/anchor/local-search-service.d.ts +72 -0
  31. package/dist/domains/anchor/services/anchor/local-search-service.d.ts.map +1 -0
  32. package/dist/domains/anchor/services/anchor/local-search-service.js +129 -0
  33. package/dist/domains/anchor/services/anchor/local-search-service.js.map +1 -0
  34. package/dist/domains/anchor/services/anchor/n-hop-search-service.d.ts +90 -0
  35. package/dist/domains/anchor/services/anchor/n-hop-search-service.d.ts.map +1 -0
  36. package/dist/domains/anchor/services/anchor/n-hop-search-service.js +385 -0
  37. package/dist/domains/anchor/services/anchor/n-hop-search-service.js.map +1 -0
  38. package/dist/domains/anchor/services/anchor/n-hop-search-strategy.d.ts +24 -0
  39. package/dist/domains/anchor/services/anchor/n-hop-search-strategy.d.ts.map +1 -0
  40. package/dist/domains/anchor/services/anchor/n-hop-search-strategy.js +39 -0
  41. package/dist/domains/anchor/services/anchor/n-hop-search-strategy.js.map +1 -0
  42. package/dist/domains/anchor/services/anchor/query-filter-service.d.ts +52 -0
  43. package/dist/domains/anchor/services/anchor/query-filter-service.d.ts.map +1 -0
  44. package/dist/domains/anchor/services/anchor/query-filter-service.js +136 -0
  45. package/dist/domains/anchor/services/anchor/query-filter-service.js.map +1 -0
  46. package/dist/domains/anchor/services/anchor/query-filter-strategy.d.ts +24 -0
  47. package/dist/domains/anchor/services/anchor/query-filter-strategy.d.ts.map +1 -0
  48. package/dist/domains/anchor/services/anchor/query-filter-strategy.js +34 -0
  49. package/dist/domains/anchor/services/anchor/query-filter-strategy.js.map +1 -0
  50. package/dist/domains/anchor/services/anchor/search-strategy-interfaces.d.ts +52 -0
  51. package/dist/domains/anchor/services/anchor/search-strategy-interfaces.d.ts.map +1 -0
  52. package/dist/domains/anchor/services/anchor/search-strategy-interfaces.js +6 -0
  53. package/dist/domains/anchor/services/anchor/search-strategy-interfaces.js.map +1 -0
  54. package/dist/domains/anchor/services/anchor/vector-search-engine-types.d.ts +18 -0
  55. package/dist/domains/anchor/services/anchor/vector-search-engine-types.d.ts.map +1 -0
  56. package/dist/domains/anchor/services/anchor/vector-search-engine-types.js +11 -0
  57. package/dist/domains/anchor/services/anchor/vector-search-engine-types.js.map +1 -0
  58. package/dist/domains/memory/repositories/core-memory-database.interface.d.ts +47 -0
  59. package/dist/domains/memory/repositories/core-memory-database.interface.d.ts.map +1 -0
  60. package/dist/domains/memory/repositories/core-memory-database.interface.js +7 -0
  61. package/dist/domains/memory/repositories/core-memory-database.interface.js.map +1 -0
  62. package/dist/domains/memory/repositories/core-memory-repository.d.ts +7 -77
  63. package/dist/domains/memory/repositories/core-memory-repository.d.ts.map +1 -1
  64. package/dist/domains/memory/repositories/core-memory-repository.interface.d.ts +92 -0
  65. package/dist/domains/memory/repositories/core-memory-repository.interface.d.ts.map +1 -0
  66. package/dist/domains/memory/repositories/core-memory-repository.interface.js +6 -0
  67. package/dist/domains/memory/repositories/core-memory-repository.interface.js.map +1 -0
  68. package/dist/domains/memory/repositories/core-memory-repository.js +7 -259
  69. package/dist/domains/memory/repositories/core-memory-repository.js.map +1 -1
  70. package/dist/domains/memory/services/core-memory-cache-service.d.ts +55 -0
  71. package/dist/domains/memory/services/core-memory-cache-service.d.ts.map +1 -1
  72. package/dist/domains/memory/services/core-memory-cache-service.js +110 -8
  73. package/dist/domains/memory/services/core-memory-cache-service.js.map +1 -1
  74. package/dist/domains/memory/services/core-memory-service.d.ts +2 -2
  75. package/dist/domains/memory/services/core-memory-service.d.ts.map +1 -1
  76. package/dist/domains/memory/services/core-memory-service.js +43 -24
  77. package/dist/domains/memory/services/core-memory-service.js.map +1 -1
  78. package/dist/domains/memory/tools/recall-tool.d.ts.map +1 -1
  79. package/dist/domains/memory/tools/recall-tool.js +2 -2
  80. package/dist/domains/memory/tools/recall-tool.js.map +1 -1
  81. package/dist/domains/memory/tools/remember-tool.d.ts.map +1 -1
  82. package/dist/domains/memory/tools/remember-tool.js +2 -2
  83. package/dist/domains/memory/tools/remember-tool.js.map +1 -1
  84. package/dist/domains/monitoring/services/performance-monitor.d.ts +8 -0
  85. package/dist/domains/monitoring/services/performance-monitor.d.ts.map +1 -1
  86. package/dist/domains/monitoring/services/performance-monitor.js +16 -0
  87. package/dist/domains/monitoring/services/performance-monitor.js.map +1 -1
  88. package/dist/infrastructure/async-optimizer.d.ts.map +1 -1
  89. package/dist/infrastructure/async-optimizer.js +8 -7
  90. package/dist/infrastructure/async-optimizer.js.map +1 -1
  91. package/dist/infrastructure/database/adapters/sqlite-core-memory-adapter.d.ts +31 -0
  92. package/dist/infrastructure/database/adapters/sqlite-core-memory-adapter.d.ts.map +1 -0
  93. package/dist/infrastructure/database/adapters/sqlite-core-memory-adapter.js +112 -0
  94. package/dist/infrastructure/database/adapters/sqlite-core-memory-adapter.js.map +1 -0
  95. package/dist/infrastructure/database/database/init.d.ts.map +1 -1
  96. package/dist/infrastructure/database/database/init.js +23 -2
  97. package/dist/infrastructure/database/database/init.js.map +1 -1
  98. package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.d.ts +64 -0
  99. package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.d.ts.map +1 -0
  100. package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.js +174 -0
  101. package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.js.map +1 -0
  102. package/dist/infrastructure/database/database/migration/migrations/010-add-core-memory-version.sql +25 -0
  103. package/dist/infrastructure/database/database-lock-monitor.d.ts +105 -0
  104. package/dist/infrastructure/database/database-lock-monitor.d.ts.map +1 -0
  105. package/dist/infrastructure/database/database-lock-monitor.js +265 -0
  106. package/dist/infrastructure/database/database-lock-monitor.js.map +1 -0
  107. package/dist/infrastructure/database/factories/core-memory-repository.factory.d.ts +17 -0
  108. package/dist/infrastructure/database/factories/core-memory-repository.factory.d.ts.map +1 -0
  109. package/dist/infrastructure/database/factories/core-memory-repository.factory.js +43 -0
  110. package/dist/infrastructure/database/factories/core-memory-repository.factory.js.map +1 -0
  111. package/dist/infrastructure/database/repositories/core-memory-repository-sqlite.impl.d.ts +63 -0
  112. package/dist/infrastructure/database/repositories/core-memory-repository-sqlite.impl.d.ts.map +1 -0
  113. package/dist/infrastructure/database/repositories/core-memory-repository-sqlite.impl.js +281 -0
  114. package/dist/infrastructure/database/repositories/core-memory-repository-sqlite.impl.js.map +1 -0
  115. package/dist/infrastructure/database/wal-checkpoint-scheduler.d.ts +166 -0
  116. package/dist/infrastructure/database/wal-checkpoint-scheduler.d.ts.map +1 -0
  117. package/dist/infrastructure/database/wal-checkpoint-scheduler.js +285 -0
  118. package/dist/infrastructure/database/wal-checkpoint-scheduler.js.map +1 -0
  119. package/dist/npm-client/memento-client.d.ts.map +1 -1
  120. package/dist/npm-client/memento-client.js +5 -4
  121. package/dist/npm-client/memento-client.js.map +1 -1
  122. package/dist/npm-client/memory-manager.d.ts.map +1 -1
  123. package/dist/npm-client/memory-manager.js +4 -3
  124. package/dist/npm-client/memory-manager.js.map +1 -1
  125. package/dist/scripts/check-migration-status.d.ts.map +1 -1
  126. package/dist/scripts/check-migration-status.js +1 -0
  127. package/dist/scripts/check-migration-status.js.map +1 -1
  128. package/dist/server/bootstrap.d.ts +4 -0
  129. package/dist/server/bootstrap.d.ts.map +1 -1
  130. package/dist/server/bootstrap.js +27 -2
  131. package/dist/server/bootstrap.js.map +1 -1
  132. package/dist/server/http-server.d.ts.map +1 -1
  133. package/dist/server/http-server.js +59 -31
  134. package/dist/server/http-server.js.map +1 -1
  135. package/dist/server/index.js +32 -16
  136. package/dist/server/index.js.map +1 -1
  137. package/dist/server/simple-mcp-server.d.ts.map +1 -1
  138. package/dist/server/simple-mcp-server.js +15 -14
  139. package/dist/server/simple-mcp-server.js.map +1 -1
  140. package/dist/shared/config/environment.d.ts.map +1 -1
  141. package/dist/shared/config/environment.js +13 -1
  142. package/dist/shared/config/environment.js.map +1 -1
  143. package/dist/shared/config/index.d.ts.map +1 -1
  144. package/dist/shared/config/index.js +13 -1
  145. package/dist/shared/config/index.js.map +1 -1
  146. package/dist/shared/types/index.d.ts +10 -0
  147. package/dist/shared/types/index.d.ts.map +1 -1
  148. package/dist/tools/base-tool.d.ts.map +1 -1
  149. package/dist/tools/base-tool.js +5 -4
  150. package/dist/tools/base-tool.js.map +1 -1
  151. package/dist/tools/migrate-embeddings-tool.d.ts.map +1 -1
  152. package/dist/tools/migrate-embeddings-tool.js +2 -1
  153. package/dist/tools/migrate-embeddings-tool.js.map +1 -1
  154. package/dist/tools/tool-registry.d.ts.map +1 -1
  155. package/dist/tools/tool-registry.js +4 -3
  156. package/dist/tools/tool-registry.js.map +1 -1
  157. package/dist/workers/consolidation-score-worker.d.ts.map +1 -1
  158. package/dist/workers/consolidation-score-worker.js +5 -4
  159. package/dist/workers/consolidation-score-worker.js.map +1 -1
  160. package/package.json +1 -1
  161. package/scripts/check-file-sizes.ts +405 -0
  162. package/scripts/count-any-types.ts +375 -0
  163. 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
- // N-hop 검색 구현 (use_relations 옵션에 따라 관계 그래프 사용 여부 결정)
84
- const allHopResults = await this.searchNHop(anchorEmbedding.embedding, anchorEmbedding.provider, anchorMemoryId, vectorThreshold, finalHopLimit, limit * 2, // 더 많이 가져와서 필터링 후 최종 limit 적용
85
- useRelations // 관계 그래프 사용 여부 전달
86
- );
87
- // 쿼리가 있는 경우 쿼리 기반 필터링
88
- let filteredResults = allHopResults;
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 체크 (query가 있을 때만, min_results 미만 시)
121
- let fallbackUsed = false;
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: finalResults,
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
- * 1-hop 검색: 앵커와 직접적으로 유사한 메모리 검색
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
- getRelationTypeBoost(relationType) {
634
- const boostMap = {
635
- 'CAUSES': 1.2,
636
- 'DEPENDS_ON': 1.1,
637
- 'FOLLOWS': 1.0,
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 boostMap[relationType] || 1.0;
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
- getSlotConfig(slot) {
690
- const slotConfig = {
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
- const nearbyMemories = await this.searchNHop(anchorEmbedding.embedding, anchorEmbedding.provider, anchorMemoryId, slotConfig.vector_threshold * 0.8, slotConfig.hop_limit, 20);
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);