remove observations from entity create

This commit is contained in:
phernandez
2025-01-26 22:30:09 -06:00
parent e2e979e705
commit deb52a71d9
11 changed files with 25 additions and 103 deletions
-1
View File
@@ -183,7 +183,6 @@ class Entity(BaseModel):
description="MIME type of the content (e.g. text/markdown, image/jpeg)",
examples=["text/markdown", "image/jpeg"],
)
observations: List[Observation] = []
@property
def permalink(self) -> PathId:
+5 -11
View File
@@ -9,10 +9,10 @@ from basic_memory.models import Entity as EntityModel, Observation, Relation
from basic_memory.repository.entity_repository import EntityRepository
from basic_memory.schemas import Entity as EntitySchema
from basic_memory.services.exceptions import EntityNotFoundError
from . import FileService
from . import BaseService
from .link_resolver import LinkResolver
from ..markdown.entity_parser import parse
from basic_memory.services import FileService
from basic_memory.services import BaseService
from basic_memory.services.link_resolver import LinkResolver
from basic_memory.markdown.entity_parser import parse
def entity_model(entity: EntitySchema):
@@ -23,7 +23,6 @@ def entity_model(entity: EntitySchema):
permalink=entity.permalink,
file_path=entity.file_path,
content_type=entity.content_type,
observations=[Observation(content=observation) for observation in entity.observations],
)
return model
@@ -94,12 +93,7 @@ class EntityService(BaseService[EntityModel]):
]
db_entity = None
try:
# set timestamps for observations if present
for observation in model.observations:
observation.created_at = observation.created_at or datetime.now(timezone.utc)
observation.updated_at = observation.updated_at or datetime.now(timezone.utc)
try:
# Create entity in DB
db_entity = await self.repository.add(model)
-3
View File
@@ -20,7 +20,6 @@ async def create_entity(client) -> EntityResponse:
data = {
"title": "TestEntity",
"entity_type": "test",
"observations": ["First observation", "Second observation"],
}
# Create an entity
response = await client.post("/knowledge/entities", json={"entities": [data]})
@@ -34,8 +33,6 @@ async def create_entity(client) -> EntityResponse:
entity_type = entity.get("entity_type")
assert entity_type == data["entity_type"]
assert len(entity["observations"]) == 2
create_response = EntityListResponse.model_validate(response_data)
return create_response.entities[0]
+1 -2
View File
@@ -129,7 +129,6 @@ async def test_reindex(client, search_service, entity_service, session_maker):
EntitySchema(
title="TestEntity1",
entity_type="test",
observations=["this is a test observation"],
),
)
@@ -151,7 +150,7 @@ async def test_reindex(client, search_service, entity_service, session_maker):
# Verify content is searchable again
search_response = await client.post("/search/", json={"text": "test"})
search_results = SearchResponse.model_validate(search_response.json())
assert len(search_results.results) == 2
assert len(search_results.results) == 1
-19
View File
@@ -42,30 +42,11 @@ def test_entity_data():
"title": "Test Entity",
"entity_type": "test",
"summary": "", # Empty string instead of None
"observations": ["This is a test observation"],
}
]
}
@pytest_asyncio.fixture
def test_directory_entity_data():
"""Real data that caused failure in the tool."""
return {
"entities": [
{
"title": "Directory Organization",
"entity_type": "memory",
"summary": "Implemented filesystem organization by entity type",
"observations": [
"Files are now organized by type using directories like entities/project/basic_memory",
"Entity IDs match filesystem paths for better mental model",
"Fixed path handling bugs by adding consistent get_entity_path helper",
],
}
]
}
@pytest_asyncio.fixture(autouse=True)
async def init_search_index(search_service: SearchService):
await search_service.init_search_index()
+4 -5
View File
@@ -99,12 +99,12 @@ async def test_add_observations_with_context(client):
@pytest.mark.asyncio
async def test_add_observations_preserves_existing(client):
async def test_add_observations(client):
"""Test that adding observations preserves existing ones."""
# Create entity with initial observation
entity_request = CreateEntityRequest(
entities=[
Entity(title="TestEntity", entity_type="test", observations=["Initial observation"])
Entity(title="TestEntity", entity_type="test")
]
)
result = await create_entities(entity_request)
@@ -119,10 +119,9 @@ async def test_add_observations_preserves_existing(client):
)
updated = await add_observations(request)
# Should have both observations
assert len(updated.observations) == 2
# Should have observation
assert len(updated.observations) == 1
contents = {obs.content for obs in updated.observations}
assert "Initial observation" in contents
assert "New observation" in contents
+8 -4
View File
@@ -15,7 +15,7 @@ async def test_create_basic_entity(client):
Entity(
title="TestEntity",
entity_type="test",
observations=["First observation"],
content="- [note] First observation",
)
]
)
@@ -49,7 +49,11 @@ async def test_create_entity_with_multiple_observations(client):
Entity(
title="TestEntity",
entity_type="test",
observations=["First observation", "Second observation", "Third observation"],
content="""
- [note] First observation
- [note] Second observation
- [note] Third observation
""",
)
]
)
@@ -72,8 +76,8 @@ async def test_create_multiple_entities(client):
"""Test creating multiple entities in one request."""
request = CreateEntityRequest(
entities=[
Entity(title="Entity1", entity_type="test", observations=["Observation 1"]),
Entity(title="Entity2", entity_type="test", observations=["Observation 2"]),
Entity(title="Entity1", entity_type="test", content="- [note] Observation 1"),
Entity(title="Entity2", entity_type="test", content="- [note] Observation 2"),
]
)
+5 -3
View File
@@ -3,7 +3,6 @@
import pytest
from httpx import AsyncClient
from mcp.server import FastMCP
from mcp.server.fastmcp import Context
from mcp.types import TextContent
from basic_memory.mcp.tools import get_entities
@@ -23,7 +22,7 @@ async def test_open_multiple_entities(mcp: FastMCP, client: AsyncClient):
Entity(title="Entity2", entity_type="test"),
]
)
result = await mcp.call_tool("create_entities",{ "request": entity_request})
result = await mcp.call_tool("create_entities", {"request": entity_request})
assert len(result) == 1
assert isinstance(result[0], TextContent)
@@ -54,7 +53,10 @@ async def test_open_nodes_with_details(client):
Entity(
title="DetailedEntity",
entity_type="test",
observations=["First observation", "Second observation"],
content="""
- [note] First observation
- [note] Second observation
""",
)
]
)
+1 -1
View File
@@ -18,7 +18,7 @@ async def test_get_basic_entity(client):
Entity(
title="TestEntity",
entity_type="test",
observations=["First observation"],
content="- [note] First observation",
)
]
)
+1 -38
View File
@@ -15,28 +15,14 @@ from basic_memory.schemas import (
from basic_memory.schemas.base import to_snake_case, TimeFrame
def test_entity_in_minimal():
def test_entity():
"""Test creating EntityIn with minimal required fields."""
data = {"title": "test_entity", "entity_type": "knowledge"}
entity = Entity.model_validate(data)
assert entity.title == "test_entity"
assert entity.entity_type == "knowledge"
assert entity.observations == []
def test_entity_in_complete():
"""Test creating EntityIn with all fields."""
data = {
"title": "test_entity",
"entity_type": "knowledge",
"observations": ["Test observation"],
}
entity = Entity.model_validate(data)
assert entity.title == "test_entity"
assert entity.entity_type == "knowledge"
assert len(entity.observations) == 1
assert entity.observations[0] == "Test observation"
def test_entity_in_validation():
"""Test validation errors for EntityIn."""
@@ -126,29 +112,6 @@ def test_entity_out_from_attributes():
assert len(entity.relations) == 1
def test_optional_fields():
"""Test handling of optional fields."""
# Create with no optional fields
entity = Entity.model_validate({"title": "test", "entity_type": "knowledge"})
assert entity.observations == []
# Create with empty optional fields
entity = Entity.model_validate(
{
"title": "test",
"entity_type": "knowledge",
"observations": [],
}
)
assert entity.observations == []
# Create with some optional fields
entity = Entity.model_validate(
{"title": "test", "entity_type": "knowledge", "observations": []}
)
assert entity.observations == []
def test_search_nodes_input():
"""Test SearchNodesInput validation."""
search = SearchNodesRequest.model_validate({"query": "test query"})
-16
View File
@@ -19,7 +19,6 @@ async def test_create_entity(entity_service: EntityService, file_service: FileSe
entity_data = EntitySchema(
title="TestEntity",
entity_type="test",
observations=["this is a test observation"],
)
# Act
@@ -32,7 +31,6 @@ async def test_create_entity(entity_service: EntityService, file_service: FileSe
assert entity.file_path == entity_data.file_path
assert entity.entity_type == "test"
assert entity.created_at is not None
assert entity.observations[0].content == "this is a test observation"
assert len(entity.relations) == 0
# Verify we can retrieve it using permalink
@@ -40,7 +38,6 @@ async def test_create_entity(entity_service: EntityService, file_service: FileSe
assert retrieved.title == "TestEntity"
assert retrieved.entity_type == "test"
assert retrieved.created_at is not None
assert retrieved.observations[0].content == "this is a test observation"
# Verify file was written
file_path = file_service.get_entity_path(entity)
@@ -63,12 +60,10 @@ async def test_create_entities(entity_service: EntityService, file_service: File
EntitySchema(
title="TestEntity1",
entity_type="test",
observations=["this is a test observation"],
),
EntitySchema(
title="TestEntity2",
entity_type="test",
observations=["this is a test observation"],
),
]
@@ -82,7 +77,6 @@ async def test_create_entities(entity_service: EntityService, file_service: File
assert entity1.title == "TestEntity1"
assert entity1.entity_type == "test"
assert entity1.created_at is not None
assert entity1.observations[0].content == "this is a test observation"
assert len(entity1.relations) == 0
entity2 = entities[1]
@@ -90,7 +84,6 @@ async def test_create_entities(entity_service: EntityService, file_service: File
assert entity2.title == "TestEntity2"
assert entity2.entity_type == "test"
assert entity2.created_at is not None
assert entity2.observations[0].content == "this is a test observation"
# Verify we can retrieve them using permalinks
retrieved1 = await entity_service.get_by_permalink(entity_data[0].permalink)
@@ -110,14 +103,12 @@ async def test_get_by_permalink(entity_service: EntityService):
entity1_data = EntitySchema(
title="TestEntity1",
entity_type="test",
observations=[],
)
entity1 = await entity_service.create_entity(entity1_data)
entity2_data = EntitySchema(
title="TestEntity2",
entity_type="test",
observations=[],
)
entity2 = await entity_service.create_entity(entity2_data)
@@ -143,7 +134,6 @@ async def test_get_entity_success(entity_service: EntityService):
entity_data = EntitySchema(
title="TestEntity",
entity_type="test",
observations=[],
)
await entity_service.create_entity(entity_data)
@@ -160,7 +150,6 @@ async def test_delete_entity_success(entity_service: EntityService):
entity_data = EntitySchema(
title="TestEntity",
entity_type="test",
observations=[],
)
await entity_service.create_entity(entity_data)
@@ -205,12 +194,10 @@ async def test_open_nodes_by_permalinks(entity_service: EntityService):
entity1_data = EntitySchema(
title="Entity1",
entity_type="test",
observations=[],
)
entity2_data = EntitySchema(
title="Entity2",
entity_type="test",
observations=[],
)
await entity_service.create_entity(entity1_data)
await entity_service.create_entity(entity2_data)
@@ -236,7 +223,6 @@ async def test_open_nodes_some_not_found(entity_service: EntityService):
entity_data = EntitySchema(
title="Entity1",
entity_type="test",
observations=[],
)
await entity_service.create_entity(entity_data)
@@ -254,12 +240,10 @@ async def test_delete_entities_by_permalinks(entity_service: EntityService):
entity1_data = EntitySchema(
title="Entity1",
entity_type="test",
observations=[],
)
entity2_data = EntitySchema(
title="Entity2",
entity_type="test",
observations=[],
)
await entity_service.create_entity(entity1_data)
await entity_service.create_entity(entity2_data)