mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
add tags to observation
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Writer for knowledge entity markdown files."""
|
||||
from typing import Optional
|
||||
|
||||
from basic_memory.models import Entity as EntityModel
|
||||
from basic_memory.models import Entity as EntityModel, Observation
|
||||
|
||||
|
||||
class KnowledgeWriter:
|
||||
@@ -25,6 +25,20 @@ class KnowledgeWriter:
|
||||
frontmatter.update(entity.entity_metadata)
|
||||
return frontmatter
|
||||
|
||||
async def format_observation(self, obs: Observation) -> str:
|
||||
"""Format a single observation with category, content, tags and context."""
|
||||
line = f"- [{obs.category}] {obs.content}"
|
||||
|
||||
# Add tags if present
|
||||
if obs.tags:
|
||||
line += " " + " ".join(f"#{tag}" for tag in sorted(obs.tags))
|
||||
|
||||
# Add context if present
|
||||
if obs.context:
|
||||
line += f" ({obs.context})"
|
||||
|
||||
return line
|
||||
|
||||
async def format_content(self, entity: EntityModel, content: Optional[str] = None) -> str:
|
||||
"""Format entity content as markdown.
|
||||
|
||||
@@ -58,15 +72,12 @@ class KnowledgeWriter:
|
||||
sections.extend([
|
||||
"## Observations",
|
||||
"<!-- Format: - [category] Content text #tag1 #tag2 (optional context) -->",
|
||||
"", # Empty line after format comment
|
||||
"",
|
||||
])
|
||||
|
||||
|
||||
for obs in entity.observations:
|
||||
line = f"- [{obs.category}] {obs.content}"
|
||||
if obs.context:
|
||||
line += f" ({obs.context})"
|
||||
sections.append(line)
|
||||
sections.append("") # Empty line after observations
|
||||
sections.append(await self.format_observation(obs))
|
||||
sections.append("")
|
||||
|
||||
# Add relations if present
|
||||
if entity.outgoing_relations:
|
||||
|
||||
@@ -121,6 +121,13 @@ class Observation(Base):
|
||||
server_default=ObservationCategory.NOTE.value,
|
||||
)
|
||||
context: Mapped[str] = mapped_column(Text, nullable=True)
|
||||
tags: Mapped[Optional[list[str]]] = mapped_column(
|
||||
JSON,
|
||||
nullable=True,
|
||||
default=list,
|
||||
server_default='[]'
|
||||
)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=text("CURRENT_TIMESTAMP"))
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime, server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP")
|
||||
|
||||
@@ -148,9 +148,9 @@ async def test_format_content_preserves_spacing(
|
||||
# <!-- Format comment -->
|
||||
# <empty line>
|
||||
# - observation entries...
|
||||
assert "<!--" in lines[i+1], "Missing format comment after Observations"
|
||||
assert lines[i+2] == "", "Missing empty line after format comment"
|
||||
assert lines[i+3].startswith("- "), "Should start observations after empty line"
|
||||
assert "<!--" in lines[i + 1], "Missing format comment after Observations"
|
||||
assert lines[i + 2] == "", "Missing empty line after format comment"
|
||||
assert lines[i + 3].startswith("- "), "Should start observations after empty line"
|
||||
|
||||
elif line == "## Relations":
|
||||
# Relations section should have format:
|
||||
@@ -158,10 +158,11 @@ async def test_format_content_preserves_spacing(
|
||||
# <!-- Format comment -->
|
||||
# <empty line>
|
||||
# - relation entries...
|
||||
assert "<!--" in lines[i+1], "Missing format comment after Relations"
|
||||
assert lines[i+2] == "", "Missing empty line after format comment"
|
||||
if i+3 < len(lines): # If there are relations
|
||||
assert lines[i+3].startswith("- "), "Should start relations after empty line"
|
||||
assert "<!--" in lines[i + 1], "Missing format comment after Relations"
|
||||
assert lines[i + 2] == "", "Missing empty line after format comment"
|
||||
if i + 3 < len(lines): # If there are relations
|
||||
assert lines[i + 3].startswith("- "), "Should start relations after empty line"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_format_content_mixed(
|
||||
@@ -172,19 +173,64 @@ async def test_format_content_mixed(
|
||||
"""Test content with both raw content and structured data."""
|
||||
# Add observations to entity with relations
|
||||
entity_with_relations.observations = entity_with_observations.observations
|
||||
|
||||
|
||||
# Test with raw content
|
||||
raw_content = "# Custom Title\n\nSome content."
|
||||
result = await knowledge_writer.format_content(entity_with_relations, raw_content)
|
||||
|
||||
|
||||
# Should preserve raw content
|
||||
assert result == raw_content
|
||||
assert "# test_entity" not in result
|
||||
|
||||
|
||||
# Test without raw content - should generate structured
|
||||
result = await knowledge_writer.format_content(entity_with_relations)
|
||||
|
||||
|
||||
assert "## Observations" in result
|
||||
assert "## Relations" in result
|
||||
assert "- [tech] First observation" in result
|
||||
assert "- connects_to [[target_entity]]" in result
|
||||
assert "- connects_to [[target_entity]]" in result
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_format_content_preserves_tags(
|
||||
knowledge_writer: KnowledgeWriter, sample_entity: Entity
|
||||
):
|
||||
"""Test that observation tags are preserved in formatting."""
|
||||
sample_entity.observations = [
|
||||
Observation(
|
||||
entity_id=1, category="tech", content="First observation", tags=["important", "bug"]
|
||||
),
|
||||
Observation(
|
||||
entity_id=1,
|
||||
category="design",
|
||||
content="Second observation",
|
||||
tags=["feature"],
|
||||
context="Some context",
|
||||
),
|
||||
Observation(entity_id=1, category="note", content="Third observation without tags"),
|
||||
]
|
||||
|
||||
result = await knowledge_writer.format_content(sample_entity)
|
||||
|
||||
# Check that tags are formatted correctly
|
||||
assert (
|
||||
"- [tech] First observation #important #bug" in result
|
||||
or "- [tech] First observation #bug #important" in result
|
||||
)
|
||||
assert "- [design] Second observation #feature (Some context)" in result
|
||||
assert "- [note] Third observation without tags" in result
|
||||
|
||||
# Verify formatting with multiple elements
|
||||
lines = result.split("\n")
|
||||
obs_section = False
|
||||
for line in lines:
|
||||
if line == "## Observations":
|
||||
obs_section = True
|
||||
continue
|
||||
if obs_section and line.startswith("- "):
|
||||
if "First observation" in line:
|
||||
# Tags should be space-separated and sorted
|
||||
assert " #bug #important" in line
|
||||
if "Second observation" in line:
|
||||
# Tags should appear before context
|
||||
assert "#feature (Some context)" in line
|
||||
|
||||
Reference in New Issue
Block a user