basic-memory 0.12.3__py3-none-any.whl → 0.13.0__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.
Potentially problematic release.
This version of basic-memory might be problematic. Click here for more details.
- basic_memory/__init__.py +2 -1
- basic_memory/alembic/env.py +1 -1
- basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +108 -0
- basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +104 -0
- basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +0 -6
- basic_memory/api/app.py +43 -13
- basic_memory/api/routers/__init__.py +4 -2
- basic_memory/api/routers/directory_router.py +63 -0
- basic_memory/api/routers/importer_router.py +152 -0
- basic_memory/api/routers/knowledge_router.py +139 -37
- basic_memory/api/routers/management_router.py +78 -0
- basic_memory/api/routers/memory_router.py +6 -62
- basic_memory/api/routers/project_router.py +234 -0
- basic_memory/api/routers/prompt_router.py +260 -0
- basic_memory/api/routers/search_router.py +3 -21
- basic_memory/api/routers/utils.py +130 -0
- basic_memory/api/template_loader.py +292 -0
- basic_memory/cli/app.py +20 -21
- basic_memory/cli/commands/__init__.py +2 -1
- basic_memory/cli/commands/auth.py +136 -0
- basic_memory/cli/commands/db.py +3 -3
- basic_memory/cli/commands/import_chatgpt.py +31 -207
- basic_memory/cli/commands/import_claude_conversations.py +16 -142
- basic_memory/cli/commands/import_claude_projects.py +33 -143
- basic_memory/cli/commands/import_memory_json.py +26 -83
- basic_memory/cli/commands/mcp.py +71 -18
- basic_memory/cli/commands/project.py +102 -70
- basic_memory/cli/commands/status.py +19 -9
- basic_memory/cli/commands/sync.py +44 -58
- basic_memory/cli/commands/tool.py +6 -6
- basic_memory/cli/main.py +1 -5
- basic_memory/config.py +143 -87
- basic_memory/db.py +6 -4
- basic_memory/deps.py +227 -30
- basic_memory/importers/__init__.py +27 -0
- basic_memory/importers/base.py +79 -0
- basic_memory/importers/chatgpt_importer.py +222 -0
- basic_memory/importers/claude_conversations_importer.py +172 -0
- basic_memory/importers/claude_projects_importer.py +148 -0
- basic_memory/importers/memory_json_importer.py +93 -0
- basic_memory/importers/utils.py +58 -0
- basic_memory/markdown/entity_parser.py +5 -2
- basic_memory/mcp/auth_provider.py +270 -0
- basic_memory/mcp/external_auth_provider.py +321 -0
- basic_memory/mcp/project_session.py +103 -0
- basic_memory/mcp/prompts/__init__.py +2 -0
- basic_memory/mcp/prompts/continue_conversation.py +18 -68
- basic_memory/mcp/prompts/recent_activity.py +20 -4
- basic_memory/mcp/prompts/search.py +14 -140
- basic_memory/mcp/prompts/sync_status.py +116 -0
- basic_memory/mcp/prompts/utils.py +3 -3
- basic_memory/mcp/{tools → resources}/project_info.py +6 -2
- basic_memory/mcp/server.py +86 -13
- basic_memory/mcp/supabase_auth_provider.py +463 -0
- basic_memory/mcp/tools/__init__.py +24 -0
- basic_memory/mcp/tools/build_context.py +43 -8
- basic_memory/mcp/tools/canvas.py +17 -3
- basic_memory/mcp/tools/delete_note.py +168 -5
- basic_memory/mcp/tools/edit_note.py +303 -0
- basic_memory/mcp/tools/list_directory.py +154 -0
- basic_memory/mcp/tools/move_note.py +299 -0
- basic_memory/mcp/tools/project_management.py +332 -0
- basic_memory/mcp/tools/read_content.py +15 -6
- basic_memory/mcp/tools/read_note.py +26 -7
- basic_memory/mcp/tools/recent_activity.py +11 -2
- basic_memory/mcp/tools/search.py +189 -8
- basic_memory/mcp/tools/sync_status.py +254 -0
- basic_memory/mcp/tools/utils.py +184 -12
- basic_memory/mcp/tools/view_note.py +66 -0
- basic_memory/mcp/tools/write_note.py +24 -17
- basic_memory/models/__init__.py +3 -2
- basic_memory/models/knowledge.py +16 -4
- basic_memory/models/project.py +78 -0
- basic_memory/models/search.py +8 -5
- basic_memory/repository/__init__.py +2 -0
- basic_memory/repository/entity_repository.py +8 -3
- basic_memory/repository/observation_repository.py +35 -3
- basic_memory/repository/project_info_repository.py +3 -2
- basic_memory/repository/project_repository.py +85 -0
- basic_memory/repository/relation_repository.py +8 -2
- basic_memory/repository/repository.py +107 -15
- basic_memory/repository/search_repository.py +192 -54
- basic_memory/schemas/__init__.py +6 -0
- basic_memory/schemas/base.py +33 -5
- basic_memory/schemas/directory.py +30 -0
- basic_memory/schemas/importer.py +34 -0
- basic_memory/schemas/memory.py +84 -13
- basic_memory/schemas/project_info.py +112 -2
- basic_memory/schemas/prompt.py +90 -0
- basic_memory/schemas/request.py +56 -2
- basic_memory/schemas/search.py +1 -1
- basic_memory/services/__init__.py +2 -1
- basic_memory/services/context_service.py +208 -95
- basic_memory/services/directory_service.py +167 -0
- basic_memory/services/entity_service.py +399 -6
- basic_memory/services/exceptions.py +6 -0
- basic_memory/services/file_service.py +14 -15
- basic_memory/services/initialization.py +170 -66
- basic_memory/services/link_resolver.py +35 -12
- basic_memory/services/migration_service.py +168 -0
- basic_memory/services/project_service.py +671 -0
- basic_memory/services/search_service.py +77 -2
- basic_memory/services/sync_status_service.py +181 -0
- basic_memory/sync/background_sync.py +25 -0
- basic_memory/sync/sync_service.py +102 -21
- basic_memory/sync/watch_service.py +63 -39
- basic_memory/templates/prompts/continue_conversation.hbs +110 -0
- basic_memory/templates/prompts/search.hbs +101 -0
- {basic_memory-0.12.3.dist-info → basic_memory-0.13.0.dist-info}/METADATA +24 -2
- basic_memory-0.13.0.dist-info/RECORD +138 -0
- basic_memory/api/routers/project_info_router.py +0 -274
- basic_memory/mcp/main.py +0 -24
- basic_memory-0.12.3.dist-info/RECORD +0 -100
- {basic_memory-0.12.3.dist-info → basic_memory-0.13.0.dist-info}/WHEEL +0 -0
- {basic_memory-0.12.3.dist-info → basic_memory-0.13.0.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.12.3.dist-info → basic_memory-0.13.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Router for prompt-related operations.
|
|
2
|
+
|
|
3
|
+
This router is responsible for rendering various prompts using Handlebars templates.
|
|
4
|
+
It centralizes all prompt formatting logic that was previously in the MCP prompts.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from fastapi import APIRouter, HTTPException, status
|
|
9
|
+
from loguru import logger
|
|
10
|
+
|
|
11
|
+
from basic_memory.api.routers.utils import to_graph_context, to_search_results
|
|
12
|
+
from basic_memory.api.template_loader import template_loader
|
|
13
|
+
from basic_memory.schemas.base import parse_timeframe
|
|
14
|
+
from basic_memory.deps import (
|
|
15
|
+
ContextServiceDep,
|
|
16
|
+
EntityRepositoryDep,
|
|
17
|
+
SearchServiceDep,
|
|
18
|
+
EntityServiceDep,
|
|
19
|
+
)
|
|
20
|
+
from basic_memory.schemas.prompt import (
|
|
21
|
+
ContinueConversationRequest,
|
|
22
|
+
SearchPromptRequest,
|
|
23
|
+
PromptResponse,
|
|
24
|
+
PromptMetadata,
|
|
25
|
+
)
|
|
26
|
+
from basic_memory.schemas.search import SearchItemType, SearchQuery
|
|
27
|
+
|
|
28
|
+
router = APIRouter(prefix="/prompt", tags=["prompt"])
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@router.post("/continue-conversation", response_model=PromptResponse)
|
|
32
|
+
async def continue_conversation(
|
|
33
|
+
search_service: SearchServiceDep,
|
|
34
|
+
entity_service: EntityServiceDep,
|
|
35
|
+
context_service: ContextServiceDep,
|
|
36
|
+
entity_repository: EntityRepositoryDep,
|
|
37
|
+
request: ContinueConversationRequest,
|
|
38
|
+
) -> PromptResponse:
|
|
39
|
+
"""Generate a prompt for continuing a conversation.
|
|
40
|
+
|
|
41
|
+
This endpoint takes a topic and/or timeframe and generates a prompt with
|
|
42
|
+
relevant context from the knowledge base.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
request: The request parameters
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Formatted continuation prompt with context
|
|
49
|
+
"""
|
|
50
|
+
logger.info(
|
|
51
|
+
f"Generating continue conversation prompt, topic: {request.topic}, timeframe: {request.timeframe}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
since = parse_timeframe(request.timeframe) if request.timeframe else None
|
|
55
|
+
|
|
56
|
+
# Initialize search results
|
|
57
|
+
search_results = []
|
|
58
|
+
|
|
59
|
+
# Get data needed for template
|
|
60
|
+
if request.topic:
|
|
61
|
+
query = SearchQuery(text=request.topic, after_date=request.timeframe)
|
|
62
|
+
results = await search_service.search(query, limit=request.search_items_limit)
|
|
63
|
+
search_results = await to_search_results(entity_service, results)
|
|
64
|
+
|
|
65
|
+
# Build context from results
|
|
66
|
+
all_hierarchical_results = []
|
|
67
|
+
for result in search_results:
|
|
68
|
+
if hasattr(result, "permalink") and result.permalink:
|
|
69
|
+
# Get hierarchical context using the new dataclass-based approach
|
|
70
|
+
context_result = await context_service.build_context(
|
|
71
|
+
result.permalink,
|
|
72
|
+
depth=request.depth,
|
|
73
|
+
since=since,
|
|
74
|
+
max_related=request.related_items_limit,
|
|
75
|
+
include_observations=True, # Include observations for entities
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Process results into the schema format
|
|
79
|
+
graph_context = await to_graph_context(
|
|
80
|
+
context_result, entity_repository=entity_repository
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Add results to our collection (limit to top results for each permalink)
|
|
84
|
+
if graph_context.results:
|
|
85
|
+
all_hierarchical_results.extend(graph_context.results[:3])
|
|
86
|
+
|
|
87
|
+
# Limit to a reasonable number of total results
|
|
88
|
+
all_hierarchical_results = all_hierarchical_results[:10]
|
|
89
|
+
|
|
90
|
+
template_context = {
|
|
91
|
+
"topic": request.topic,
|
|
92
|
+
"timeframe": request.timeframe,
|
|
93
|
+
"hierarchical_results": all_hierarchical_results,
|
|
94
|
+
"has_results": len(all_hierarchical_results) > 0,
|
|
95
|
+
}
|
|
96
|
+
else:
|
|
97
|
+
# If no topic, get recent activity
|
|
98
|
+
context_result = await context_service.build_context(
|
|
99
|
+
types=[SearchItemType.ENTITY],
|
|
100
|
+
depth=request.depth,
|
|
101
|
+
since=since,
|
|
102
|
+
max_related=request.related_items_limit,
|
|
103
|
+
include_observations=True,
|
|
104
|
+
)
|
|
105
|
+
recent_context = await to_graph_context(context_result, entity_repository=entity_repository)
|
|
106
|
+
|
|
107
|
+
hierarchical_results = recent_context.results[:5] # Limit to top 5 recent items
|
|
108
|
+
|
|
109
|
+
template_context = {
|
|
110
|
+
"topic": f"Recent Activity from ({request.timeframe})",
|
|
111
|
+
"timeframe": request.timeframe,
|
|
112
|
+
"hierarchical_results": hierarchical_results,
|
|
113
|
+
"has_results": len(hierarchical_results) > 0,
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
# Render template
|
|
118
|
+
rendered_prompt = await template_loader.render(
|
|
119
|
+
"prompts/continue_conversation.hbs", template_context
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Calculate metadata
|
|
123
|
+
# Count items of different types
|
|
124
|
+
observation_count = 0
|
|
125
|
+
relation_count = 0
|
|
126
|
+
entity_count = 0
|
|
127
|
+
|
|
128
|
+
# Get the hierarchical results from the template context
|
|
129
|
+
hierarchical_results_for_count = template_context.get("hierarchical_results", [])
|
|
130
|
+
|
|
131
|
+
# For topic-based search
|
|
132
|
+
if request.topic:
|
|
133
|
+
for item in hierarchical_results_for_count:
|
|
134
|
+
if hasattr(item, "observations"):
|
|
135
|
+
observation_count += len(item.observations) if item.observations else 0
|
|
136
|
+
|
|
137
|
+
if hasattr(item, "related_results"):
|
|
138
|
+
for related in item.related_results or []:
|
|
139
|
+
if hasattr(related, "type"):
|
|
140
|
+
if related.type == "relation":
|
|
141
|
+
relation_count += 1
|
|
142
|
+
elif related.type == "entity": # pragma: no cover
|
|
143
|
+
entity_count += 1 # pragma: no cover
|
|
144
|
+
# For recent activity
|
|
145
|
+
else:
|
|
146
|
+
for item in hierarchical_results_for_count:
|
|
147
|
+
if hasattr(item, "observations"):
|
|
148
|
+
observation_count += len(item.observations) if item.observations else 0
|
|
149
|
+
|
|
150
|
+
if hasattr(item, "related_results"):
|
|
151
|
+
for related in item.related_results or []:
|
|
152
|
+
if hasattr(related, "type"):
|
|
153
|
+
if related.type == "relation":
|
|
154
|
+
relation_count += 1
|
|
155
|
+
elif related.type == "entity": # pragma: no cover
|
|
156
|
+
entity_count += 1 # pragma: no cover
|
|
157
|
+
|
|
158
|
+
# Build metadata
|
|
159
|
+
metadata = {
|
|
160
|
+
"query": request.topic,
|
|
161
|
+
"timeframe": request.timeframe,
|
|
162
|
+
"search_count": len(search_results)
|
|
163
|
+
if request.topic
|
|
164
|
+
else 0, # Original search results count
|
|
165
|
+
"context_count": len(hierarchical_results_for_count),
|
|
166
|
+
"observation_count": observation_count,
|
|
167
|
+
"relation_count": relation_count,
|
|
168
|
+
"total_items": (
|
|
169
|
+
len(hierarchical_results_for_count)
|
|
170
|
+
+ observation_count
|
|
171
|
+
+ relation_count
|
|
172
|
+
+ entity_count
|
|
173
|
+
),
|
|
174
|
+
"search_limit": request.search_items_limit,
|
|
175
|
+
"context_depth": request.depth,
|
|
176
|
+
"related_limit": request.related_items_limit,
|
|
177
|
+
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
prompt_metadata = PromptMetadata(**metadata)
|
|
181
|
+
|
|
182
|
+
return PromptResponse(
|
|
183
|
+
prompt=rendered_prompt, context=template_context, metadata=prompt_metadata
|
|
184
|
+
)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
logger.error(f"Error rendering continue conversation template: {e}")
|
|
187
|
+
raise HTTPException(
|
|
188
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
189
|
+
detail=f"Error rendering prompt template: {str(e)}",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@router.post("/search", response_model=PromptResponse)
|
|
194
|
+
async def search_prompt(
|
|
195
|
+
search_service: SearchServiceDep,
|
|
196
|
+
entity_service: EntityServiceDep,
|
|
197
|
+
request: SearchPromptRequest,
|
|
198
|
+
page: int = 1,
|
|
199
|
+
page_size: int = 10,
|
|
200
|
+
) -> PromptResponse:
|
|
201
|
+
"""Generate a prompt for search results.
|
|
202
|
+
|
|
203
|
+
This endpoint takes a search query and formats the results into a helpful
|
|
204
|
+
prompt with context and suggestions.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
request: The search parameters
|
|
208
|
+
page: The page number for pagination
|
|
209
|
+
page_size: The number of results per page, defaults to 10
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Formatted search results prompt with context
|
|
213
|
+
"""
|
|
214
|
+
logger.info(f"Generating search prompt, query: {request.query}, timeframe: {request.timeframe}")
|
|
215
|
+
|
|
216
|
+
limit = page_size
|
|
217
|
+
offset = (page - 1) * page_size
|
|
218
|
+
|
|
219
|
+
query = SearchQuery(text=request.query, after_date=request.timeframe)
|
|
220
|
+
results = await search_service.search(query, limit=limit, offset=offset)
|
|
221
|
+
search_results = await to_search_results(entity_service, results)
|
|
222
|
+
|
|
223
|
+
template_context = {
|
|
224
|
+
"query": request.query,
|
|
225
|
+
"timeframe": request.timeframe,
|
|
226
|
+
"results": search_results,
|
|
227
|
+
"has_results": len(search_results) > 0,
|
|
228
|
+
"result_count": len(search_results),
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# Render template
|
|
233
|
+
rendered_prompt = await template_loader.render("prompts/search.hbs", template_context)
|
|
234
|
+
|
|
235
|
+
# Build metadata
|
|
236
|
+
metadata = {
|
|
237
|
+
"query": request.query,
|
|
238
|
+
"timeframe": request.timeframe,
|
|
239
|
+
"search_count": len(search_results),
|
|
240
|
+
"context_count": len(search_results),
|
|
241
|
+
"observation_count": 0, # Search results don't include observations
|
|
242
|
+
"relation_count": 0, # Search results don't include relations
|
|
243
|
+
"total_items": len(search_results),
|
|
244
|
+
"search_limit": limit,
|
|
245
|
+
"context_depth": 0, # No context depth for basic search
|
|
246
|
+
"related_limit": 0, # No related items for basic search
|
|
247
|
+
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
prompt_metadata = PromptMetadata(**metadata)
|
|
251
|
+
|
|
252
|
+
return PromptResponse(
|
|
253
|
+
prompt=rendered_prompt, context=template_context, metadata=prompt_metadata
|
|
254
|
+
)
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.error(f"Error rendering search template: {e}")
|
|
257
|
+
raise HTTPException(
|
|
258
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
259
|
+
detail=f"Error rendering prompt template: {str(e)}",
|
|
260
|
+
)
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, BackgroundTasks
|
|
4
4
|
|
|
5
|
-
from basic_memory.
|
|
5
|
+
from basic_memory.api.routers.utils import to_search_results
|
|
6
|
+
from basic_memory.schemas.search import SearchQuery, SearchResponse
|
|
6
7
|
from basic_memory.deps import SearchServiceDep, EntityServiceDep
|
|
7
8
|
|
|
8
9
|
router = APIRouter(prefix="/search", tags=["search"])
|
|
@@ -20,26 +21,7 @@ async def search(
|
|
|
20
21
|
limit = page_size
|
|
21
22
|
offset = (page - 1) * page_size
|
|
22
23
|
results = await search_service.search(query, limit=limit, offset=offset)
|
|
23
|
-
|
|
24
|
-
search_results = []
|
|
25
|
-
for r in results:
|
|
26
|
-
entities = await entity_service.get_entities_by_id([r.entity_id, r.from_id, r.to_id]) # pyright: ignore
|
|
27
|
-
search_results.append(
|
|
28
|
-
SearchResult(
|
|
29
|
-
title=r.title, # pyright: ignore
|
|
30
|
-
type=r.type, # pyright: ignore
|
|
31
|
-
permalink=r.permalink,
|
|
32
|
-
score=r.score, # pyright: ignore
|
|
33
|
-
entity=entities[0].permalink if entities else None,
|
|
34
|
-
content=r.content,
|
|
35
|
-
file_path=r.file_path,
|
|
36
|
-
metadata=r.metadata,
|
|
37
|
-
category=r.category,
|
|
38
|
-
from_entity=entities[0].permalink if entities else None,
|
|
39
|
-
to_entity=entities[1].permalink if len(entities) > 1 else None,
|
|
40
|
-
relation_type=r.relation_type,
|
|
41
|
-
)
|
|
42
|
-
)
|
|
24
|
+
search_results = await to_search_results(entity_service, results)
|
|
43
25
|
return SearchResponse(
|
|
44
26
|
results=search_results,
|
|
45
27
|
current_page=page,
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
|
|
3
|
+
from basic_memory.repository import EntityRepository
|
|
4
|
+
from basic_memory.repository.search_repository import SearchIndexRow
|
|
5
|
+
from basic_memory.schemas.memory import (
|
|
6
|
+
EntitySummary,
|
|
7
|
+
ObservationSummary,
|
|
8
|
+
RelationSummary,
|
|
9
|
+
MemoryMetadata,
|
|
10
|
+
GraphContext,
|
|
11
|
+
ContextResult,
|
|
12
|
+
)
|
|
13
|
+
from basic_memory.schemas.search import SearchItemType, SearchResult
|
|
14
|
+
from basic_memory.services import EntityService
|
|
15
|
+
from basic_memory.services.context_service import (
|
|
16
|
+
ContextResultRow,
|
|
17
|
+
ContextResult as ServiceContextResult,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def to_graph_context(
|
|
22
|
+
context_result: ServiceContextResult,
|
|
23
|
+
entity_repository: EntityRepository,
|
|
24
|
+
page: Optional[int] = None,
|
|
25
|
+
page_size: Optional[int] = None,
|
|
26
|
+
):
|
|
27
|
+
# Helper function to convert items to summaries
|
|
28
|
+
async def to_summary(item: SearchIndexRow | ContextResultRow):
|
|
29
|
+
match item.type:
|
|
30
|
+
case SearchItemType.ENTITY:
|
|
31
|
+
return EntitySummary(
|
|
32
|
+
title=item.title, # pyright: ignore
|
|
33
|
+
permalink=item.permalink,
|
|
34
|
+
content=item.content,
|
|
35
|
+
file_path=item.file_path,
|
|
36
|
+
created_at=item.created_at,
|
|
37
|
+
)
|
|
38
|
+
case SearchItemType.OBSERVATION:
|
|
39
|
+
return ObservationSummary(
|
|
40
|
+
title=item.title, # pyright: ignore
|
|
41
|
+
file_path=item.file_path,
|
|
42
|
+
category=item.category, # pyright: ignore
|
|
43
|
+
content=item.content, # pyright: ignore
|
|
44
|
+
permalink=item.permalink, # pyright: ignore
|
|
45
|
+
created_at=item.created_at,
|
|
46
|
+
)
|
|
47
|
+
case SearchItemType.RELATION:
|
|
48
|
+
from_entity = await entity_repository.find_by_id(item.from_id) # pyright: ignore
|
|
49
|
+
to_entity = await entity_repository.find_by_id(item.to_id) if item.to_id else None
|
|
50
|
+
return RelationSummary(
|
|
51
|
+
title=item.title, # pyright: ignore
|
|
52
|
+
file_path=item.file_path,
|
|
53
|
+
permalink=item.permalink, # pyright: ignore
|
|
54
|
+
relation_type=item.relation_type, # pyright: ignore
|
|
55
|
+
from_entity=from_entity.title, # pyright: ignore
|
|
56
|
+
to_entity=to_entity.title if to_entity else None,
|
|
57
|
+
created_at=item.created_at,
|
|
58
|
+
)
|
|
59
|
+
case _: # pragma: no cover
|
|
60
|
+
raise ValueError(f"Unexpected type: {item.type}")
|
|
61
|
+
|
|
62
|
+
# Process the hierarchical results
|
|
63
|
+
hierarchical_results = []
|
|
64
|
+
for context_item in context_result.results:
|
|
65
|
+
# Process primary result
|
|
66
|
+
primary_result = await to_summary(context_item.primary_result)
|
|
67
|
+
|
|
68
|
+
# Process observations
|
|
69
|
+
observations = []
|
|
70
|
+
for obs in context_item.observations:
|
|
71
|
+
observations.append(await to_summary(obs))
|
|
72
|
+
|
|
73
|
+
# Process related results
|
|
74
|
+
related = []
|
|
75
|
+
for rel in context_item.related_results:
|
|
76
|
+
related.append(await to_summary(rel))
|
|
77
|
+
|
|
78
|
+
# Add to hierarchical results
|
|
79
|
+
hierarchical_results.append(
|
|
80
|
+
ContextResult(
|
|
81
|
+
primary_result=primary_result,
|
|
82
|
+
observations=observations,
|
|
83
|
+
related_results=related,
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Create schema metadata from service metadata
|
|
88
|
+
metadata = MemoryMetadata(
|
|
89
|
+
uri=context_result.metadata.uri,
|
|
90
|
+
types=context_result.metadata.types,
|
|
91
|
+
depth=context_result.metadata.depth,
|
|
92
|
+
timeframe=context_result.metadata.timeframe,
|
|
93
|
+
generated_at=context_result.metadata.generated_at,
|
|
94
|
+
primary_count=context_result.metadata.primary_count,
|
|
95
|
+
related_count=context_result.metadata.related_count,
|
|
96
|
+
total_results=context_result.metadata.primary_count + context_result.metadata.related_count,
|
|
97
|
+
total_relations=context_result.metadata.total_relations,
|
|
98
|
+
total_observations=context_result.metadata.total_observations,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Return new GraphContext with just hierarchical results
|
|
102
|
+
return GraphContext(
|
|
103
|
+
results=hierarchical_results,
|
|
104
|
+
metadata=metadata,
|
|
105
|
+
page=page,
|
|
106
|
+
page_size=page_size,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def to_search_results(entity_service: EntityService, results: List[SearchIndexRow]):
|
|
111
|
+
search_results = []
|
|
112
|
+
for r in results:
|
|
113
|
+
entities = await entity_service.get_entities_by_id([r.entity_id, r.from_id, r.to_id]) # pyright: ignore
|
|
114
|
+
search_results.append(
|
|
115
|
+
SearchResult(
|
|
116
|
+
title=r.title, # pyright: ignore
|
|
117
|
+
type=r.type, # pyright: ignore
|
|
118
|
+
permalink=r.permalink,
|
|
119
|
+
score=r.score, # pyright: ignore
|
|
120
|
+
entity=entities[0].permalink if entities else None,
|
|
121
|
+
content=r.content,
|
|
122
|
+
file_path=r.file_path,
|
|
123
|
+
metadata=r.metadata,
|
|
124
|
+
category=r.category,
|
|
125
|
+
from_entity=entities[0].permalink if entities else None,
|
|
126
|
+
to_entity=entities[1].permalink if len(entities) > 1 else None,
|
|
127
|
+
relation_type=r.relation_type,
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
return search_results
|