add delete_note tool

This commit is contained in:
phernandez
2025-01-21 20:08:52 -06:00
parent 18d4584652
commit 526baead18
5 changed files with 178 additions and 27 deletions
@@ -9,7 +9,8 @@ from basic_memory.deps import (
EntityServiceDep,
get_search_service,
RelationServiceDep,
ObservationServiceDep, SearchServiceDep,
ObservationServiceDep,
SearchServiceDep, LinkResolverDep,
)
from basic_memory.schemas import (
CreateEntityRequest,
@@ -21,7 +22,6 @@ from basic_memory.schemas import (
DeleteObservationsRequest,
DeleteRelationsRequest,
DeleteEntitiesRequest,
UpdateEntityRequest,
)
from basic_memory.schemas.base import PathId, Entity
from basic_memory.services.exceptions import EntityNotFoundError
@@ -30,33 +30,31 @@ router = APIRouter(prefix="/knowledge", tags=["knowledge"])
## Create endpoints
@router.put("/entities/{permalink:path}", response_model=EntityResponse)
async def create_or_update_entity(
permalink: PathId,
data: Entity,
response: Response,
background_tasks: BackgroundTasks,
entity_service: EntityServiceDep,
search_service: SearchServiceDep,
permalink: PathId,
data: Entity,
response: Response,
background_tasks: BackgroundTasks,
entity_service: EntityServiceDep,
search_service: SearchServiceDep,
) -> EntityResponse:
"""Create or update an entity. If entity exists, it will be updated, otherwise created."""
# Validate permalink matches
if data.permalink != permalink:
raise HTTPException(
status_code=400,
detail="Entity permalink must match URL path"
)
raise HTTPException(status_code=400, detail="Entity permalink must match URL path")
# Try create_or_update operation
entity, created = await entity_service.create_or_update_entity(data)
response.status_code = 201 if created else 200
# Always reindex since content has changed
await search_service.index_entity(entity, background_tasks=background_tasks)
return EntityResponse.model_validate(entity)
@router.post("/entities", response_model=EntityListResponse)
async def create_entities(
data: CreateEntityRequest,
@@ -76,7 +74,6 @@ async def create_entities(
)
@router.post("/relations", response_model=EntityListResponse)
async def create_relations(
data: CreateRelationsRequest,
@@ -154,6 +151,29 @@ async def get_entities(
## Delete endpoints
@router.delete("/entities/{identifier:path}", response_model=DeleteEntitiesResponse)
async def delete_entity(
identifier: str,
background_tasks: BackgroundTasks,
entity_service: EntityServiceDep,
link_resolver: LinkResolverDep,
search_service=Depends(get_search_service),
) -> DeleteEntitiesResponse:
"""Delete a single entity and remove from search index."""
entity = await link_resolver.resolve_link(identifier)
if entity is None:
return DeleteEntitiesResponse(deleted=False)
# Delete the entity
deleted = await entity_service.delete_entity(entity.permalink)
# Remove from search index
background_tasks.add_task(search_service.delete_by_permalink, entity.permalink)
return DeleteEntitiesResponse(deleted=deleted)
@router.post("/entities/delete", response_model=DeleteEntitiesResponse)
async def delete_entities(
data: DeleteEntitiesRequest,
+32 -11
View File
@@ -10,12 +10,11 @@ from loguru import logger
from basic_memory.mcp.server import mcp
from basic_memory.mcp.async_client import client
from basic_memory.schemas import EntityResponse
from basic_memory.schemas.request import CreateEntityRequest
from basic_memory.schemas import EntityResponse, DeleteEntitiesResponse
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, call_put
from basic_memory.mcp.tools.knowledge import create_relations
from basic_memory.mcp.tools.utils import call_get, call_put, call_delete
@mcp.tool(
@@ -55,18 +54,18 @@ async def write_note(
# Create the entity request
metadata = {"tags": [f"#{tag}" for tag in tags]} if tags else None
entity = Entity(
title=title,
entity_type="note",
content_type="text/markdown",
content=content,
entity_metadata=metadata,
)
title=title,
entity_type="note",
content_type="text/markdown",
content=content,
entity_metadata=metadata,
)
# Use existing knowledge tool
logger.info(f"Creating {entity.permalink}")
url = f"/knowledge/entities/{entity.permalink}"
response = await call_put(client, url, json=entity.model_dump())
result = EntityResponse.model_validate(response.json())
result = EntityResponse.model_validate(response.json())
return result.permalink
@@ -137,3 +136,25 @@ async def link_notes(
)
response = await create_relations(request)
return response.entities[0].permalink
@mcp.tool(description="Delete a note by title or permalink")
async def delete_note(identifier: str) -> bool:
"""Delete a note from the knowledge base.
Args:
identifier: Note title or permalink
Returns:
True if note was deleted, False otherwise
Examples:
# Delete by title
delete_note("Meeting Notes: Project Planning")
# Delete by permalink
delete_note("notes/project-planning")
"""
response = await call_delete(client, f"/knowledge/entities/{identifier}")
result = DeleteEntitiesResponse.model_validate(response.json())
return result.deleted
+30 -1
View File
@@ -29,7 +29,6 @@ async def call_get(
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
extensions: RequestExtensions | None = None,
) -> Response:
logger.debug(f"Calling GET '{url}' params: '{params}'")
try:
response = await client.get(
@@ -123,3 +122,33 @@ async def call_post(
except HTTPStatusError as e:
logger.error(f"Error calling POST {url}: {e}")
raise ToolError(f"Error calling tool: {e}") from e
async def call_delete(
client: AsyncClient,
url: URL | str,
*,
params: QueryParamTypes | None = None,
headers: HeaderTypes | None = None,
cookies: CookieTypes | None = None,
auth: AuthTypes | UseClientDefault = USE_CLIENT_DEFAULT,
follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
extensions: RequestExtensions | None = None,
) -> Response:
try:
response = await client.delete(
url=url,
params=params,
headers=headers,
cookies=cookies,
auth=auth,
follow_redirects=follow_redirects,
timeout=timeout,
extensions=extensions,
)
response.raise_for_status()
return response
except HTTPStatusError as e:
logger.error(f"Error calling DELETE {url}: {e}")
raise ToolError(f"Error calling tool: {e}") from e