mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
remove observations from entity create
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
""",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ async def test_get_basic_entity(client):
|
||||
Entity(
|
||||
title="TestEntity",
|
||||
entity_type="test",
|
||||
observations=["First observation"],
|
||||
content="- [note] First observation",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -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"})
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user