mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
add fuzzy matching to notes
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user