memorygraphMCP 0.11.7__py3-none-any.whl
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.
- memorygraph/__init__.py +50 -0
- memorygraph/__main__.py +12 -0
- memorygraph/advanced_tools.py +509 -0
- memorygraph/analytics/__init__.py +46 -0
- memorygraph/analytics/advanced_queries.py +727 -0
- memorygraph/backends/__init__.py +21 -0
- memorygraph/backends/base.py +179 -0
- memorygraph/backends/cloud.py +75 -0
- memorygraph/backends/cloud_backend.py +858 -0
- memorygraph/backends/factory.py +577 -0
- memorygraph/backends/falkordb_backend.py +749 -0
- memorygraph/backends/falkordblite_backend.py +746 -0
- memorygraph/backends/ladybugdb_backend.py +242 -0
- memorygraph/backends/memgraph_backend.py +327 -0
- memorygraph/backends/neo4j_backend.py +298 -0
- memorygraph/backends/sqlite_fallback.py +463 -0
- memorygraph/backends/turso.py +448 -0
- memorygraph/cli.py +743 -0
- memorygraph/cloud_database.py +297 -0
- memorygraph/config.py +295 -0
- memorygraph/database.py +933 -0
- memorygraph/graph_analytics.py +631 -0
- memorygraph/integration/__init__.py +69 -0
- memorygraph/integration/context_capture.py +426 -0
- memorygraph/integration/project_analysis.py +583 -0
- memorygraph/integration/workflow_tracking.py +492 -0
- memorygraph/intelligence/__init__.py +59 -0
- memorygraph/intelligence/context_retrieval.py +447 -0
- memorygraph/intelligence/entity_extraction.py +386 -0
- memorygraph/intelligence/pattern_recognition.py +420 -0
- memorygraph/intelligence/temporal.py +374 -0
- memorygraph/migration/__init__.py +27 -0
- memorygraph/migration/manager.py +579 -0
- memorygraph/migration/models.py +142 -0
- memorygraph/migration/scripts/__init__.py +17 -0
- memorygraph/migration/scripts/bitemporal_migration.py +595 -0
- memorygraph/migration/scripts/multitenancy_migration.py +452 -0
- memorygraph/migration_tools_module.py +146 -0
- memorygraph/models.py +684 -0
- memorygraph/proactive/__init__.py +46 -0
- memorygraph/proactive/outcome_learning.py +444 -0
- memorygraph/proactive/predictive.py +410 -0
- memorygraph/proactive/session_briefing.py +399 -0
- memorygraph/relationships.py +668 -0
- memorygraph/server.py +883 -0
- memorygraph/sqlite_database.py +1876 -0
- memorygraph/tools/__init__.py +59 -0
- memorygraph/tools/activity_tools.py +262 -0
- memorygraph/tools/memory_tools.py +315 -0
- memorygraph/tools/migration_tools.py +181 -0
- memorygraph/tools/relationship_tools.py +147 -0
- memorygraph/tools/search_tools.py +406 -0
- memorygraph/tools/temporal_tools.py +339 -0
- memorygraph/utils/__init__.py +10 -0
- memorygraph/utils/context_extractor.py +429 -0
- memorygraph/utils/error_handling.py +151 -0
- memorygraph/utils/export_import.py +425 -0
- memorygraph/utils/graph_algorithms.py +200 -0
- memorygraph/utils/pagination.py +149 -0
- memorygraph/utils/project_detection.py +133 -0
- memorygraphmcp-0.11.7.dist-info/METADATA +970 -0
- memorygraphmcp-0.11.7.dist-info/RECORD +65 -0
- memorygraphmcp-0.11.7.dist-info/WHEEL +4 -0
- memorygraphmcp-0.11.7.dist-info/entry_points.txt +2 -0
- memorygraphmcp-0.11.7.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Predictive Suggestions for Claude Code Memory Server.
|
|
3
|
+
|
|
4
|
+
Provides proactive suggestions based on current context:
|
|
5
|
+
- Predict relevant memories and patterns
|
|
6
|
+
- Warn about potential issues
|
|
7
|
+
- Suggest related context
|
|
8
|
+
|
|
9
|
+
Phase 7 Implementation - Predictive Suggestions
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import List, Optional, Dict, Any
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, Field
|
|
17
|
+
|
|
18
|
+
from ..backends.base import GraphBackend
|
|
19
|
+
from ..models import Memory, MemoryType, RelationshipType
|
|
20
|
+
from ..intelligence.entity_extraction import extract_entities, Entity
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Suggestion(BaseModel):
|
|
26
|
+
"""
|
|
27
|
+
Proactive suggestion for relevant memory or pattern.
|
|
28
|
+
|
|
29
|
+
Suggestions are ranked by relevance score.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
suggestion_id: str
|
|
33
|
+
suggestion_type: str # "memory", "pattern", "solution"
|
|
34
|
+
title: str
|
|
35
|
+
description: str
|
|
36
|
+
relevance_score: float = Field(..., ge=0.0, le=1.0)
|
|
37
|
+
reason: str # Why this is suggested
|
|
38
|
+
memory_id: str
|
|
39
|
+
tags: List[str] = Field(default_factory=list)
|
|
40
|
+
effectiveness: Optional[float] = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Warning(BaseModel):
|
|
44
|
+
"""
|
|
45
|
+
Warning about potential issues.
|
|
46
|
+
|
|
47
|
+
Based on known problems and patterns.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
warning_id: str
|
|
51
|
+
severity: str # "low", "medium", "high"
|
|
52
|
+
title: str
|
|
53
|
+
description: str
|
|
54
|
+
evidence: List[str] = Field(default_factory=list) # Memory IDs that support this warning
|
|
55
|
+
mitigation: Optional[str] = None # Suggested mitigation
|
|
56
|
+
related_problem_id: Optional[str] = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def predict_needs(
|
|
60
|
+
backend: GraphBackend,
|
|
61
|
+
current_context: str,
|
|
62
|
+
max_suggestions: int = 5,
|
|
63
|
+
min_relevance: float = 0.3,
|
|
64
|
+
) -> List[Suggestion]:
|
|
65
|
+
"""
|
|
66
|
+
Predict relevant memories and patterns based on current context.
|
|
67
|
+
|
|
68
|
+
Uses entity extraction and relationship matching to find relevant information.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
backend: Database backend
|
|
72
|
+
current_context: Current work context (e.g., file content, task description)
|
|
73
|
+
max_suggestions: Maximum number of suggestions to return
|
|
74
|
+
min_relevance: Minimum relevance score threshold
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of suggestions ranked by relevance
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> suggestions = await predict_needs(backend, "Working on authentication with JWT")
|
|
81
|
+
>>> for s in suggestions:
|
|
82
|
+
... print(f"{s.title}: {s.reason}")
|
|
83
|
+
"""
|
|
84
|
+
logger.info("Predicting needs from current context")
|
|
85
|
+
|
|
86
|
+
# Extract entities from current context
|
|
87
|
+
entities = extract_entities(current_context)
|
|
88
|
+
logger.debug(f"Extracted {len(entities)} entities from context")
|
|
89
|
+
|
|
90
|
+
if not entities:
|
|
91
|
+
logger.warning("No entities extracted from context")
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
suggestions = []
|
|
95
|
+
|
|
96
|
+
# Find memories that mention the same entities
|
|
97
|
+
for entity in entities[:10]: # Limit to top 10 entities to avoid overwhelming queries
|
|
98
|
+
entity_query = """
|
|
99
|
+
MATCH (m:Memory)-[:MENTIONS]->(e:Entity {text: $entity_text})
|
|
100
|
+
WHERE m.type IN ['solution', 'code_pattern', 'fix']
|
|
101
|
+
OPTIONAL MATCH (m)-[r:EFFECTIVE_FOR|SOLVES|ADDRESSES]->()
|
|
102
|
+
RETURN m.id as id, m.type as type, m.title as title,
|
|
103
|
+
m.content as content, m.tags as tags,
|
|
104
|
+
m.effectiveness as effectiveness,
|
|
105
|
+
m.usage_count as usage_count,
|
|
106
|
+
count(r) as effectiveness_links
|
|
107
|
+
ORDER BY m.effectiveness DESC, m.usage_count DESC
|
|
108
|
+
LIMIT 3
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
results = await backend.execute_query(
|
|
113
|
+
entity_query,
|
|
114
|
+
{"entity_text": entity.text}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
for record in results:
|
|
118
|
+
# Calculate relevance score based on:
|
|
119
|
+
# - Entity match (base score)
|
|
120
|
+
# - Effectiveness
|
|
121
|
+
# - Usage count
|
|
122
|
+
# - Number of effectiveness relationships
|
|
123
|
+
base_score = entity.confidence
|
|
124
|
+
effectiveness_bonus = record.get("effectiveness", 0.5) * 0.3
|
|
125
|
+
usage_bonus = min(record.get("usage_count", 0) / 10.0, 0.2)
|
|
126
|
+
links_bonus = min(record.get("effectiveness_links", 0) / 5.0, 0.2)
|
|
127
|
+
|
|
128
|
+
relevance = base_score + effectiveness_bonus + usage_bonus + links_bonus
|
|
129
|
+
relevance = min(relevance, 1.0) # Cap at 1.0
|
|
130
|
+
|
|
131
|
+
if relevance >= min_relevance:
|
|
132
|
+
suggestions.append(Suggestion(
|
|
133
|
+
suggestion_id=record["id"],
|
|
134
|
+
suggestion_type=record["type"],
|
|
135
|
+
title=record["title"],
|
|
136
|
+
description=record["content"][:200],
|
|
137
|
+
relevance_score=relevance,
|
|
138
|
+
reason=f"Related to {entity.entity_type.value}: {entity.text}",
|
|
139
|
+
memory_id=record["id"],
|
|
140
|
+
tags=record.get("tags", []),
|
|
141
|
+
effectiveness=record.get("effectiveness"),
|
|
142
|
+
))
|
|
143
|
+
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Error querying memories for entity {entity.text}: {e}")
|
|
146
|
+
|
|
147
|
+
# Find patterns that match the context
|
|
148
|
+
pattern_keywords = [(e.text.lower(), e.confidence) for e in entities if e.entity_type.value in ["technology", "concept"]]
|
|
149
|
+
|
|
150
|
+
if pattern_keywords:
|
|
151
|
+
pattern_query = """
|
|
152
|
+
MATCH (p:Memory {type: 'code_pattern'})
|
|
153
|
+
WHERE any(keyword IN $keywords WHERE p.content CONTAINS keyword)
|
|
154
|
+
RETURN p.id as id, p.title as title, p.content as content,
|
|
155
|
+
p.tags as tags, p.effectiveness as effectiveness,
|
|
156
|
+
p.usage_count as usage_count
|
|
157
|
+
ORDER BY p.effectiveness DESC
|
|
158
|
+
LIMIT 3
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
# Get average confidence of matching entities
|
|
163
|
+
avg_confidence = sum(conf for _, conf in pattern_keywords) / len(pattern_keywords)
|
|
164
|
+
keywords_only = [kw for kw, _ in pattern_keywords]
|
|
165
|
+
|
|
166
|
+
results = await backend.execute_query(
|
|
167
|
+
pattern_query,
|
|
168
|
+
{"keywords": keywords_only}
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
for record in results:
|
|
172
|
+
# Calculate relevance for patterns based on entity confidence
|
|
173
|
+
effectiveness = record.get("effectiveness", 0.5)
|
|
174
|
+
usage_count = record.get("usage_count", 0)
|
|
175
|
+
|
|
176
|
+
# Use entity confidence as base, similar to entity-based suggestions
|
|
177
|
+
relevance = avg_confidence + (effectiveness * 0.3) + min(usage_count / 10.0, 0.1)
|
|
178
|
+
relevance = min(relevance, 1.0) # Cap at 1.0
|
|
179
|
+
|
|
180
|
+
if relevance >= min_relevance:
|
|
181
|
+
suggestions.append(Suggestion(
|
|
182
|
+
suggestion_id=record["id"],
|
|
183
|
+
suggestion_type="pattern",
|
|
184
|
+
title=record["title"],
|
|
185
|
+
description=record["content"][:200],
|
|
186
|
+
relevance_score=relevance,
|
|
187
|
+
reason="Matching pattern for current context",
|
|
188
|
+
memory_id=record["id"],
|
|
189
|
+
tags=record.get("tags", []),
|
|
190
|
+
effectiveness=record.get("effectiveness"),
|
|
191
|
+
))
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logger.error(f"Error querying patterns: {e}")
|
|
195
|
+
|
|
196
|
+
# Deduplicate and sort by relevance
|
|
197
|
+
seen_ids = set()
|
|
198
|
+
unique_suggestions = []
|
|
199
|
+
for suggestion in sorted(suggestions, key=lambda s: s.relevance_score, reverse=True):
|
|
200
|
+
if suggestion.memory_id not in seen_ids:
|
|
201
|
+
unique_suggestions.append(suggestion)
|
|
202
|
+
seen_ids.add(suggestion.memory_id)
|
|
203
|
+
|
|
204
|
+
logger.info(f"Generated {len(unique_suggestions)} suggestions")
|
|
205
|
+
return unique_suggestions[:max_suggestions]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
async def warn_potential_issues(
|
|
209
|
+
backend: GraphBackend,
|
|
210
|
+
current_context: str,
|
|
211
|
+
severity_threshold: str = "medium",
|
|
212
|
+
) -> List[Warning]:
|
|
213
|
+
"""
|
|
214
|
+
Warn about potential issues based on current context.
|
|
215
|
+
|
|
216
|
+
Matches against known problem patterns and deprecated approaches.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
backend: Database backend
|
|
220
|
+
current_context: Current work context
|
|
221
|
+
severity_threshold: Minimum severity to report ("low", "medium", "high")
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
List of warnings with evidence
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
>>> warnings = await warn_potential_issues(backend, "Using JWT authentication")
|
|
228
|
+
>>> for w in warnings:
|
|
229
|
+
... print(f"{w.severity.upper()}: {w.title}")
|
|
230
|
+
"""
|
|
231
|
+
logger.info("Checking for potential issues in current context")
|
|
232
|
+
|
|
233
|
+
# Extract entities from context
|
|
234
|
+
entities = extract_entities(current_context)
|
|
235
|
+
|
|
236
|
+
warnings = []
|
|
237
|
+
|
|
238
|
+
# Check for deprecated approaches
|
|
239
|
+
entity_texts = [e.text for e in entities]
|
|
240
|
+
|
|
241
|
+
if entity_texts:
|
|
242
|
+
deprecated_query = """
|
|
243
|
+
MATCH (old:Memory)-[r:DEPRECATED_BY]->(new:Memory)
|
|
244
|
+
MATCH (old)-[:MENTIONS]->(e:Entity)
|
|
245
|
+
WHERE e.text IN $entity_texts
|
|
246
|
+
RETURN old.id as old_id, old.title as old_title,
|
|
247
|
+
old.content as old_content,
|
|
248
|
+
new.id as new_id, new.title as new_title,
|
|
249
|
+
r.context as reason,
|
|
250
|
+
collect(e.text) as entities
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
results = await backend.execute_query(
|
|
255
|
+
deprecated_query,
|
|
256
|
+
{"entity_texts": entity_texts}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
for record in results:
|
|
260
|
+
warnings.append(Warning(
|
|
261
|
+
warning_id=record["old_id"],
|
|
262
|
+
severity="high",
|
|
263
|
+
title=f"Deprecated: {record['old_title']}",
|
|
264
|
+
description=record.get("reason", "This approach is deprecated"),
|
|
265
|
+
evidence=[record["old_id"]],
|
|
266
|
+
mitigation=f"Consider using: {record['new_title']}",
|
|
267
|
+
related_problem_id=record["old_id"],
|
|
268
|
+
))
|
|
269
|
+
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.error(f"Error checking for deprecated approaches: {e}")
|
|
272
|
+
|
|
273
|
+
# Check for known problem patterns
|
|
274
|
+
problem_keywords = [e.text.lower() for e in entities]
|
|
275
|
+
|
|
276
|
+
if problem_keywords:
|
|
277
|
+
problem_query = """
|
|
278
|
+
MATCH (p:Memory {type: 'problem'})
|
|
279
|
+
WHERE any(keyword IN $keywords WHERE p.content CONTAINS keyword)
|
|
280
|
+
OPTIONAL MATCH (p)-[:SOLVES|ADDRESSES]-(s:Memory {type: 'solution'})
|
|
281
|
+
RETURN p.id as problem_id, p.title as problem_title,
|
|
282
|
+
p.content as problem_content, p.tags as tags,
|
|
283
|
+
collect(s.id) as solution_ids,
|
|
284
|
+
collect(s.title) as solution_titles
|
|
285
|
+
LIMIT 5
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
try:
|
|
289
|
+
results = await backend.execute_query(
|
|
290
|
+
problem_query,
|
|
291
|
+
{"keywords": problem_keywords}
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
for record in results:
|
|
295
|
+
# If problem has solutions, suggest them; otherwise warn
|
|
296
|
+
has_solutions = bool(record.get("solution_ids"))
|
|
297
|
+
|
|
298
|
+
if has_solutions:
|
|
299
|
+
mitigation = f"Known solutions: {', '.join(record['solution_titles'][:2])}"
|
|
300
|
+
severity = "medium"
|
|
301
|
+
else:
|
|
302
|
+
mitigation = "No known solution yet - proceed with caution"
|
|
303
|
+
severity = "high"
|
|
304
|
+
|
|
305
|
+
warnings.append(Warning(
|
|
306
|
+
warning_id=record["problem_id"],
|
|
307
|
+
severity=severity,
|
|
308
|
+
title=f"Known issue: {record['problem_title']}",
|
|
309
|
+
description=record["problem_content"][:200],
|
|
310
|
+
evidence=[record["problem_id"]],
|
|
311
|
+
mitigation=mitigation,
|
|
312
|
+
related_problem_id=record["problem_id"],
|
|
313
|
+
))
|
|
314
|
+
|
|
315
|
+
except Exception as e:
|
|
316
|
+
logger.error(f"Error checking for known problems: {e}")
|
|
317
|
+
|
|
318
|
+
# Filter by severity threshold
|
|
319
|
+
severity_levels = {"low": 0, "medium": 1, "high": 2}
|
|
320
|
+
threshold_level = severity_levels.get(severity_threshold, 1)
|
|
321
|
+
|
|
322
|
+
filtered_warnings = [
|
|
323
|
+
w for w in warnings
|
|
324
|
+
if severity_levels.get(w.severity, 0) >= threshold_level
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
logger.info(f"Generated {len(filtered_warnings)} warnings")
|
|
328
|
+
return filtered_warnings
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
async def suggest_related_context(
|
|
332
|
+
backend: GraphBackend,
|
|
333
|
+
memory_id: str,
|
|
334
|
+
max_suggestions: int = 5,
|
|
335
|
+
) -> List[Suggestion]:
|
|
336
|
+
"""
|
|
337
|
+
Suggest related context that the user might want to know about.
|
|
338
|
+
|
|
339
|
+
"You might also want to know..." suggestions based on relationship graph.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
backend: Database backend
|
|
343
|
+
memory_id: Current memory being viewed
|
|
344
|
+
max_suggestions: Maximum number of suggestions
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
List of related suggestions
|
|
348
|
+
|
|
349
|
+
Example:
|
|
350
|
+
>>> suggestions = await suggest_related_context(backend, "mem_123")
|
|
351
|
+
>>> for s in suggestions:
|
|
352
|
+
... print(f"Related: {s.title}")
|
|
353
|
+
"""
|
|
354
|
+
logger.info(f"Suggesting related context for memory {memory_id}")
|
|
355
|
+
|
|
356
|
+
# Find related memories through strong relationships
|
|
357
|
+
related_query = """
|
|
358
|
+
MATCH (m:Memory {id: $memory_id})-[r]->(related:Memory)
|
|
359
|
+
WHERE r.strength >= 0.5
|
|
360
|
+
AND type(r) IN ['BUILDS_ON', 'CONFIRMS', 'SIMILAR_TO', 'RELATED_TO',
|
|
361
|
+
'ALTERNATIVE_TO', 'IMPROVES']
|
|
362
|
+
RETURN related.id as id, related.type as type, related.title as title,
|
|
363
|
+
related.content as content, related.tags as tags,
|
|
364
|
+
related.effectiveness as effectiveness,
|
|
365
|
+
type(r) as rel_type, r.strength as strength
|
|
366
|
+
ORDER BY r.strength DESC
|
|
367
|
+
LIMIT $limit
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
suggestions = []
|
|
371
|
+
|
|
372
|
+
try:
|
|
373
|
+
results = await backend.execute_query(
|
|
374
|
+
related_query,
|
|
375
|
+
{"memory_id": memory_id, "limit": max_suggestions}
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
for record in results:
|
|
379
|
+
rel_type = record["rel_type"]
|
|
380
|
+
strength = record.get("strength", 0.5)
|
|
381
|
+
|
|
382
|
+
# Map relationship type to reason
|
|
383
|
+
reasons = {
|
|
384
|
+
"BUILDS_ON": "Builds on this concept",
|
|
385
|
+
"CONFIRMS": "Confirms this approach",
|
|
386
|
+
"SIMILAR_TO": "Similar approach",
|
|
387
|
+
"RELATED_TO": "Related information",
|
|
388
|
+
"ALTERNATIVE_TO": "Alternative approach",
|
|
389
|
+
"IMPROVES": "Improved version",
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
reason = reasons.get(rel_type, "Related")
|
|
393
|
+
|
|
394
|
+
suggestions.append(Suggestion(
|
|
395
|
+
suggestion_id=record["id"],
|
|
396
|
+
suggestion_type=record["type"],
|
|
397
|
+
title=record["title"],
|
|
398
|
+
description=record["content"][:200],
|
|
399
|
+
relevance_score=strength,
|
|
400
|
+
reason=reason,
|
|
401
|
+
memory_id=record["id"],
|
|
402
|
+
tags=record.get("tags", []),
|
|
403
|
+
effectiveness=record.get("effectiveness"),
|
|
404
|
+
))
|
|
405
|
+
|
|
406
|
+
except Exception as e:
|
|
407
|
+
logger.error(f"Error finding related context: {e}")
|
|
408
|
+
|
|
409
|
+
logger.info(f"Found {len(suggestions)} related suggestions")
|
|
410
|
+
return suggestions[:max_suggestions]
|