add fuzzy matching to notes

This commit is contained in:
phernandez
2025-01-20 20:26:16 -06:00
parent 224b11332c
commit 5ba6d0d1f5
10 changed files with 111 additions and 92 deletions
@@ -6,24 +6,24 @@ from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse
from loguru import logger
from basic_memory.deps import EntityRepositoryDep, ProjectConfigDep
from basic_memory.deps import EntityRepositoryDep, ProjectConfigDep, LinkResolverDep
router = APIRouter(prefix="/resource", tags=["resources"])
@router.get("/{permalink:path}")
@router.get("/{identifier:path}")
async def get_resource_content(
config: ProjectConfigDep,
entity_repository: EntityRepositoryDep,
permalink: str,
link_resolver: LinkResolverDep,
identifier: str,
) -> FileResponse:
"""Get resource content by permalink."""
logger.debug(f"Getting content for permalink: {permalink}")
"""Get resource content by identifier: name or permalink."""
logger.debug(f"Getting content for permalink: {identifier}")
# Find entity by permalink
entity = await entity_repository.get_by_permalink(permalink)
entity = await link_resolver.resolve_link(identifier)
if not entity:
raise HTTPException(status_code=404, detail=f"Entity not found: {permalink}")
raise HTTPException(status_code=404, detail=f"Entity not found: {identifier}")
file_path = Path(f"{config.home}/{entity.file_path}")
if not file_path.exists():
+9 -1
View File
@@ -23,6 +23,7 @@ from basic_memory.services import (
)
from basic_memory.services.context_service import ContextService
from basic_memory.services.file_service import FileService
from basic_memory.services.link_resolver import LinkResolver
from basic_memory.services.search_service import SearchService
@@ -105,7 +106,6 @@ SearchRepositoryDep = Annotated[SearchRepository, Depends(get_search_repository)
## services
async def get_file_service(project_config: ProjectConfigDep) -> FileService:
return FileService(project_config.home, KnowledgeWriter())
@@ -143,12 +143,14 @@ async def get_relation_service(
relation_repository: RelationRepositoryDep,
entity_repository: EntityRepositoryDep,
file_service: FileServiceDep,
link_resolver: "LinkResolverDep",
) -> RelationService:
"""Create RelationService with repository."""
return RelationService(
relation_repository=relation_repository,
entity_repository=entity_repository,
file_service=file_service,
link_resolver=link_resolver,
)
@@ -171,6 +173,12 @@ async def get_knowledge_writer() -> KnowledgeWriter:
KnowledgeWriterDep = Annotated[KnowledgeWriter, Depends(get_knowledge_writer)]
async def get_link_resolver(entity_repository: EntityRepositoryDep,
search_service: SearchServiceDep) -> LinkResolver:
return LinkResolver(entity_repository=entity_repository,
search_service=search_service)
LinkResolverDep = Annotated[LinkResolver, Depends(get_link_resolver)]
async def get_context_service(
search_repository: SearchRepositoryDep, entity_repository: EntityRepositoryDep
+5 -24
View File
@@ -7,17 +7,14 @@ while leveraging the underlying knowledge graph structure.
from typing import Optional, List
from loguru import logger
from mcp.server.fastmcp.exceptions import ToolError
from basic_memory.mcp.server import mcp
from basic_memory.mcp.async_client import client
from basic_memory.mcp.tools.search import search
from basic_memory.schemas.request import CreateEntityRequest
from basic_memory.schemas.base import Entity, Relation
from basic_memory.schemas.request import CreateRelationsRequest
from basic_memory.mcp.tools.knowledge import create_entities, create_relations
from basic_memory.mcp.tools.utils import call_get
from basic_memory.schemas.search import SearchQuery
@mcp.tool(
@@ -95,25 +92,8 @@ async def read_note(identifier: str) -> str:
Raises:
ValueError: If the note cannot be found
"""
try:
# Try as permalink first
response = await call_get(client, f"/resource/{identifier}")
return response.text
except ToolError as e:
if "404" in str(e):
# If not found, try searching by title
search_response = await search(SearchQuery(text=identifier, entity_types=["note"]))
if not search_response.results:
raise ValueError(f"Note not found: {identifier}")
# if we found results, return the first one
response = await call_get(client, f"/resource/{search_response.results[0].permalink}")
return response.text
raise ValueError(f"Error reading note: {e}")
except Exception as e:
raise ValueError(f"Unexpected error reading note: {e}")
response = await call_get(client, f"/resource/{identifier}")
return response.text
@mcp.tool(description="Create a semantic link between two notes")
@@ -122,7 +102,7 @@ async def link_notes(
to_note: str,
relationship: str = "relates_to",
context: Optional[str] = None,
) -> None:
) -> str:
"""Create a semantic link between two notes.
Args:
@@ -156,4 +136,5 @@ async def link_notes(
)
]
)
await create_relations(request)
response = await create_relations(request)
return response.entities[0].permalink
@@ -9,6 +9,7 @@ from basic_memory.models import Entity as EntityModel, Relation as RelationModel
from basic_memory.repository.relation_repository import RelationRepository
from . import FileService
from .exceptions import EntityNotFoundError
from .link_resolver import LinkResolver
from .service import BaseService
from ..repository import EntityRepository
@@ -24,10 +25,12 @@ class RelationService(BaseService[RelationRepository]):
relation_repository: RelationRepository,
entity_repository: EntityRepository,
file_service: FileService,
link_resolver: LinkResolver,
):
super().__init__(relation_repository)
self.entity_repository = entity_repository
self.file_service = file_service
self.link_resolver = link_resolver
async def create_relations(self, relations: List[RelationSchema]) -> Sequence[EntityModel]:
"""Create relations and return updated entities."""
@@ -37,9 +40,10 @@ class RelationService(BaseService[RelationRepository]):
for rs in relations:
try:
from_entity = await self.entity_repository.get_by_permalink(rs.from_id)
to_entity = await self.entity_repository.get_by_permalink(rs.to_id)
# Use link resolver instead of direct permalink lookup
from_entity = await self.link_resolver.resolve_link(rs.from_id)
to_entity = await self.link_resolver.resolve_link(rs.to_id)
relation = RelationModel(
from_id=from_entity.id,
to_id=to_entity.id,
@@ -50,8 +54,7 @@ class RelationService(BaseService[RelationRepository]):
await self.repository.add(relation)
# Keep track of entities we need to update
entities_to_update.add(rs.from_id)
entities_to_update.add(rs.to_id)
entities_to_update.add(from_entity.permalink)
except Exception as e:
logger.error(f"Failed to create relation: {e}")