basic-memory 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of basic-memory might be problematic. Click here for more details.
- basic_memory/__init__.py +3 -0
- basic_memory/api/__init__.py +4 -0
- basic_memory/api/app.py +42 -0
- basic_memory/api/routers/__init__.py +8 -0
- basic_memory/api/routers/knowledge_router.py +168 -0
- basic_memory/api/routers/memory_router.py +123 -0
- basic_memory/api/routers/resource_router.py +34 -0
- basic_memory/api/routers/search_router.py +34 -0
- basic_memory/cli/__init__.py +1 -0
- basic_memory/cli/app.py +4 -0
- basic_memory/cli/commands/__init__.py +9 -0
- basic_memory/cli/commands/init.py +38 -0
- basic_memory/cli/commands/status.py +152 -0
- basic_memory/cli/commands/sync.py +254 -0
- basic_memory/cli/main.py +48 -0
- basic_memory/config.py +53 -0
- basic_memory/db.py +135 -0
- basic_memory/deps.py +182 -0
- basic_memory/file_utils.py +248 -0
- basic_memory/markdown/__init__.py +19 -0
- basic_memory/markdown/entity_parser.py +137 -0
- basic_memory/markdown/markdown_processor.py +153 -0
- basic_memory/markdown/plugins.py +236 -0
- basic_memory/markdown/schemas.py +73 -0
- basic_memory/markdown/utils.py +144 -0
- basic_memory/mcp/__init__.py +1 -0
- basic_memory/mcp/async_client.py +10 -0
- basic_memory/mcp/main.py +21 -0
- basic_memory/mcp/server.py +39 -0
- basic_memory/mcp/tools/__init__.py +34 -0
- basic_memory/mcp/tools/ai_edit.py +84 -0
- basic_memory/mcp/tools/knowledge.py +56 -0
- basic_memory/mcp/tools/memory.py +142 -0
- basic_memory/mcp/tools/notes.py +122 -0
- basic_memory/mcp/tools/search.py +28 -0
- basic_memory/mcp/tools/utils.py +154 -0
- basic_memory/models/__init__.py +12 -0
- basic_memory/models/base.py +9 -0
- basic_memory/models/knowledge.py +204 -0
- basic_memory/models/search.py +34 -0
- basic_memory/repository/__init__.py +7 -0
- basic_memory/repository/entity_repository.py +156 -0
- basic_memory/repository/observation_repository.py +40 -0
- basic_memory/repository/relation_repository.py +78 -0
- basic_memory/repository/repository.py +303 -0
- basic_memory/repository/search_repository.py +259 -0
- basic_memory/schemas/__init__.py +73 -0
- basic_memory/schemas/base.py +216 -0
- basic_memory/schemas/delete.py +38 -0
- basic_memory/schemas/discovery.py +25 -0
- basic_memory/schemas/memory.py +111 -0
- basic_memory/schemas/request.py +77 -0
- basic_memory/schemas/response.py +220 -0
- basic_memory/schemas/search.py +117 -0
- basic_memory/services/__init__.py +11 -0
- basic_memory/services/context_service.py +274 -0
- basic_memory/services/entity_service.py +281 -0
- basic_memory/services/exceptions.py +15 -0
- basic_memory/services/file_service.py +213 -0
- basic_memory/services/link_resolver.py +126 -0
- basic_memory/services/search_service.py +218 -0
- basic_memory/services/service.py +36 -0
- basic_memory/sync/__init__.py +5 -0
- basic_memory/sync/file_change_scanner.py +162 -0
- basic_memory/sync/sync_service.py +140 -0
- basic_memory/sync/utils.py +66 -0
- basic_memory/sync/watch_service.py +197 -0
- basic_memory/utils.py +78 -0
- basic_memory-0.0.0.dist-info/METADATA +71 -0
- basic_memory-0.0.0.dist-info/RECORD +73 -0
- basic_memory-0.0.0.dist-info/WHEEL +4 -0
- basic_memory-0.0.0.dist-info/entry_points.txt +2 -0
- basic_memory-0.0.0.dist-info/licenses/LICENSE +661 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"""Core pydantic models for basic-memory entities, observations, and relations.
|
|
2
|
+
|
|
3
|
+
This module defines the foundational data structures for the knowledge graph system.
|
|
4
|
+
The graph consists of entities (nodes) connected by relations (edges), where each
|
|
5
|
+
entity can have multiple observations (facts) attached to it.
|
|
6
|
+
|
|
7
|
+
Key Concepts:
|
|
8
|
+
1. Entities are nodes storing factual observations
|
|
9
|
+
2. Relations are directed edges between entities using active voice verbs
|
|
10
|
+
3. Observations are atomic facts/notes about an entity
|
|
11
|
+
4. Everything is stored in both SQLite and markdown files
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import mimetypes
|
|
15
|
+
import re
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import List, Optional, Annotated, Dict
|
|
20
|
+
|
|
21
|
+
from annotated_types import MinLen, MaxLen
|
|
22
|
+
from dateparser import parse
|
|
23
|
+
|
|
24
|
+
from pydantic import BaseModel, BeforeValidator, Field, model_validator, ValidationError
|
|
25
|
+
|
|
26
|
+
from basic_memory.utils import generate_permalink
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def to_snake_case(name: str) -> str:
|
|
30
|
+
"""Convert a string to snake_case.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
BasicMemory -> basic_memory
|
|
34
|
+
Memory Service -> memory_service
|
|
35
|
+
memory-service -> memory_service
|
|
36
|
+
Memory_Service -> memory_service
|
|
37
|
+
"""
|
|
38
|
+
name = name.strip()
|
|
39
|
+
|
|
40
|
+
# Replace spaces and hyphens and . with underscores
|
|
41
|
+
s1 = re.sub(r"[\s\-\\.]", "_", name)
|
|
42
|
+
|
|
43
|
+
# Insert underscore between camelCase
|
|
44
|
+
s2 = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1)
|
|
45
|
+
|
|
46
|
+
# Convert to lowercase
|
|
47
|
+
return s2.lower()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def validate_path_format(path: str) -> str:
|
|
51
|
+
"""Validate path has the correct format: not empty."""
|
|
52
|
+
if not path or not isinstance(path, str):
|
|
53
|
+
raise ValueError("Path must be a non-empty string")
|
|
54
|
+
|
|
55
|
+
return path
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ObservationCategory(str, Enum):
|
|
59
|
+
"""Categories for structuring observations.
|
|
60
|
+
|
|
61
|
+
Categories help organize knowledge and make it easier to find later:
|
|
62
|
+
- tech: Implementation details and technical notes
|
|
63
|
+
- design: Architecture decisions and patterns
|
|
64
|
+
- feature: User-facing capabilities
|
|
65
|
+
- note: General observations (default)
|
|
66
|
+
- issue: Problems or concerns
|
|
67
|
+
- todo: Future work items
|
|
68
|
+
|
|
69
|
+
Categories are case-insensitive for easier use.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
TECH = "tech"
|
|
73
|
+
DESIGN = "design"
|
|
74
|
+
FEATURE = "feature"
|
|
75
|
+
NOTE = "note"
|
|
76
|
+
ISSUE = "issue"
|
|
77
|
+
TODO = "todo"
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def _missing_(cls, value: str) -> "ObservationCategory":
|
|
81
|
+
"""Handle case-insensitive lookup."""
|
|
82
|
+
try:
|
|
83
|
+
return cls(value.lower())
|
|
84
|
+
except ValueError:
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def validate_timeframe(timeframe: str) -> str:
|
|
89
|
+
"""Convert human readable timeframes to a duration relative to the current time."""
|
|
90
|
+
if not isinstance(timeframe, str):
|
|
91
|
+
raise ValueError("Timeframe must be a string")
|
|
92
|
+
|
|
93
|
+
# Parse relative time expression
|
|
94
|
+
parsed = parse(timeframe)
|
|
95
|
+
if not parsed:
|
|
96
|
+
raise ValueError(f"Could not parse timeframe: {timeframe}")
|
|
97
|
+
|
|
98
|
+
# Convert to duration
|
|
99
|
+
now = datetime.now()
|
|
100
|
+
if parsed > now:
|
|
101
|
+
raise ValueError("Timeframe cannot be in the future")
|
|
102
|
+
|
|
103
|
+
# Could format the duration back to our standard format
|
|
104
|
+
days = (now - parsed).days
|
|
105
|
+
|
|
106
|
+
# Could enforce reasonable limits
|
|
107
|
+
if days > 365:
|
|
108
|
+
raise ValueError("Timeframe should be <= 1 year")
|
|
109
|
+
|
|
110
|
+
return f"{days}d"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
TimeFrame = Annotated[
|
|
114
|
+
str,
|
|
115
|
+
BeforeValidator(validate_timeframe)
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
PathId = Annotated[str, BeforeValidator(validate_path_format)]
|
|
119
|
+
"""Unique identifier in format '{path}/{normalized_name}'."""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
EntityType = Annotated[str, BeforeValidator(to_snake_case), MinLen(1), MaxLen(200)]
|
|
123
|
+
"""Classification of entity (e.g., 'person', 'project', 'concept'). """
|
|
124
|
+
|
|
125
|
+
ALLOWED_CONTENT_TYPES = {
|
|
126
|
+
"text/markdown",
|
|
127
|
+
"text/plain",
|
|
128
|
+
"application/pdf",
|
|
129
|
+
"image/jpeg",
|
|
130
|
+
"image/png",
|
|
131
|
+
"image/svg+xml",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
ContentType = Annotated[
|
|
135
|
+
str,
|
|
136
|
+
BeforeValidator(str.lower),
|
|
137
|
+
Field(pattern=r"^[\w\-\+\.]+/[\w\-\+\.]+$"),
|
|
138
|
+
Field(json_schema_extra={"examples": list(ALLOWED_CONTENT_TYPES)}),
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
RelationType = Annotated[str, MinLen(1), MaxLen(200)]
|
|
143
|
+
"""Type of relationship between entities. Always use active voice present tense."""
|
|
144
|
+
|
|
145
|
+
ObservationStr = Annotated[
|
|
146
|
+
str,
|
|
147
|
+
BeforeValidator(str.strip), # Clean whitespace
|
|
148
|
+
MinLen(1), # Ensure non-empty after stripping
|
|
149
|
+
MaxLen(1000), # Keep reasonable length
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
class Observation(BaseModel):
|
|
153
|
+
"""A single observation with category, content, and optional context."""
|
|
154
|
+
|
|
155
|
+
category: ObservationCategory
|
|
156
|
+
content: ObservationStr
|
|
157
|
+
tags: Optional[List[str]] = Field(default_factory=list)
|
|
158
|
+
context: Optional[str] = None
|
|
159
|
+
|
|
160
|
+
class Relation(BaseModel):
|
|
161
|
+
"""Represents a directed edge between entities in the knowledge graph.
|
|
162
|
+
|
|
163
|
+
Relations are directed connections stored in active voice (e.g., "created", "depends_on").
|
|
164
|
+
The from_permalink represents the source or actor entity, while to_permalink represents the target
|
|
165
|
+
or recipient entity.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
from_id: PathId
|
|
169
|
+
to_id: PathId
|
|
170
|
+
relation_type: RelationType
|
|
171
|
+
context: Optional[str] = None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class Entity(BaseModel):
|
|
175
|
+
"""Represents a node in our knowledge graph - could be a person, project, concept, etc.
|
|
176
|
+
|
|
177
|
+
Each entity has:
|
|
178
|
+
- A file path (e.g., "people/jane-doe.md")
|
|
179
|
+
- An entity type (for classification)
|
|
180
|
+
- A list of observations (facts/notes about the entity)
|
|
181
|
+
- Optional relations to other entities
|
|
182
|
+
- Optional description for high-level overview
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
title: str
|
|
186
|
+
content: Optional[str] = None
|
|
187
|
+
folder: str
|
|
188
|
+
entity_type: EntityType = "note"
|
|
189
|
+
entity_metadata: Optional[Dict] = Field(default=None, description="Optional metadata")
|
|
190
|
+
content_type: ContentType = Field(
|
|
191
|
+
description="MIME type of the content (e.g. text/markdown, image/jpeg)",
|
|
192
|
+
examples=["text/markdown", "image/jpeg"], default="text/markdown"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def file_path(self):
|
|
197
|
+
"""Get the file path for this entity based on its permalink."""
|
|
198
|
+
return f"{self.folder}/{self.title}.md" if self.folder else f"{self.title}.md"
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def permalink(self) -> PathId:
|
|
202
|
+
"""Get the path ID in format {snake_case_title}."""
|
|
203
|
+
return generate_permalink(self.file_path)
|
|
204
|
+
|
|
205
|
+
@model_validator(mode="after")
|
|
206
|
+
@classmethod
|
|
207
|
+
def infer_content_type(cls, entity: "Entity") -> Dict | None:
|
|
208
|
+
"""Infer content_type from file_path if not provided."""
|
|
209
|
+
if not entity.content_type:
|
|
210
|
+
path = Path(entity.file_path)
|
|
211
|
+
if not path.exists():
|
|
212
|
+
return None
|
|
213
|
+
mime_type, _ = mimetypes.guess_type(path.name)
|
|
214
|
+
entity.content_type = mime_type or "text/plain"
|
|
215
|
+
|
|
216
|
+
return entity
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Delete operation schemas for the knowledge graph.
|
|
2
|
+
|
|
3
|
+
This module defines the request schemas for removing entities, relations,
|
|
4
|
+
and observations from the knowledge graph. Each operation has specific
|
|
5
|
+
implications and safety considerations.
|
|
6
|
+
|
|
7
|
+
Deletion Hierarchy:
|
|
8
|
+
1. Entity deletion removes the entity and all its relations
|
|
9
|
+
2. Relation deletion only removes the connection between entities
|
|
10
|
+
3. Observation deletion preserves entity and relations
|
|
11
|
+
|
|
12
|
+
Key Considerations:
|
|
13
|
+
- All deletions are permanent
|
|
14
|
+
- Entity deletions cascade to relations
|
|
15
|
+
- Files are removed along with entities
|
|
16
|
+
- Operations are atomic - they fully succeed or fail
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import List, Annotated
|
|
20
|
+
|
|
21
|
+
from annotated_types import MinLen
|
|
22
|
+
from pydantic import BaseModel
|
|
23
|
+
|
|
24
|
+
from basic_memory.schemas.base import Relation, Observation, PathId
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DeleteEntitiesRequest(BaseModel):
|
|
28
|
+
"""Delete one or more entities from the knowledge graph.
|
|
29
|
+
|
|
30
|
+
This operation:
|
|
31
|
+
1. Removes the entity from the database
|
|
32
|
+
2. Deletes all observations attached to the entity
|
|
33
|
+
3. Removes all relations where the entity is source or target
|
|
34
|
+
4. Deletes the corresponding markdown file
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
permalinks: Annotated[List[PathId], MinLen(1)]
|
|
38
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Schemas for knowledge discovery and analytics endpoints."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
from basic_memory.schemas.response import EntityResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EntityTypeList(BaseModel):
|
|
10
|
+
"""List of unique entity types in the system."""
|
|
11
|
+
types: List[str]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ObservationCategoryList(BaseModel):
|
|
15
|
+
"""List of unique observation categories in the system."""
|
|
16
|
+
categories: List[str]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TypedEntityList(BaseModel):
|
|
20
|
+
"""List of entities of a specific type."""
|
|
21
|
+
entity_type: str = Field(..., description="Type of entities in the list")
|
|
22
|
+
entities: List[EntityResponse]
|
|
23
|
+
total: int = Field(..., description="Total number of entities")
|
|
24
|
+
sort_by: Optional[str] = Field(None, description="Field used for sorting")
|
|
25
|
+
include_related: bool = Field(False, description="Whether related entities are included")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Schemas for memory context."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Dict, List, Any, Optional, Annotated
|
|
5
|
+
|
|
6
|
+
import pydantic
|
|
7
|
+
from annotated_types import MinLen, MaxLen
|
|
8
|
+
from pydantic import BaseModel, field_validator, Field, BeforeValidator, TypeAdapter, AnyUrl
|
|
9
|
+
|
|
10
|
+
from basic_memory.schemas.search import SearchItemType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def normalize_memory_url(url: str) -> str:
|
|
14
|
+
"""Normalize a MemoryUrl string.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
url: A path like "specs/search" or "memory://specs/search"
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Normalized URL starting with memory://
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
>>> normalize_memory_url("specs/search")
|
|
24
|
+
'memory://specs/search'
|
|
25
|
+
>>> normalize_memory_url("memory://specs/search")
|
|
26
|
+
'memory://specs/search'
|
|
27
|
+
"""
|
|
28
|
+
clean_path = url.removeprefix("memory://")
|
|
29
|
+
return f"memory://{clean_path}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
MemoryUrl = Annotated[
|
|
33
|
+
str,
|
|
34
|
+
BeforeValidator(str.strip), # Clean whitespace
|
|
35
|
+
MinLen(1),
|
|
36
|
+
MaxLen(2028),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
memory_url = TypeAdapter(MemoryUrl)
|
|
40
|
+
|
|
41
|
+
def memory_url_path(url: memory_url) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Returns the uri for a url value by removing the prefix "memory://" from a given MemoryUrl.
|
|
44
|
+
|
|
45
|
+
This function processes a given MemoryUrl by removing the "memory://"
|
|
46
|
+
prefix and returns the resulting string. If the provided url does not
|
|
47
|
+
begin with "memory://", the function will simply return the input url
|
|
48
|
+
unchanged.
|
|
49
|
+
|
|
50
|
+
:param url: A MemoryUrl object representing the URL with a "memory://" prefix.
|
|
51
|
+
:type url: MemoryUrl
|
|
52
|
+
:return: A string representing the URL with the "memory://" prefix removed.
|
|
53
|
+
:rtype: str
|
|
54
|
+
"""
|
|
55
|
+
return url.removeprefix("memory://")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class EntitySummary(BaseModel):
|
|
60
|
+
"""Simplified entity representation."""
|
|
61
|
+
|
|
62
|
+
permalink: str
|
|
63
|
+
title: str
|
|
64
|
+
file_path: str
|
|
65
|
+
created_at: datetime
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class RelationSummary(BaseModel):
|
|
69
|
+
"""Simplified relation representation."""
|
|
70
|
+
|
|
71
|
+
permalink: str
|
|
72
|
+
type: str
|
|
73
|
+
from_id: str
|
|
74
|
+
to_id: Optional[str] = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ObservationSummary(BaseModel):
|
|
78
|
+
"""Simplified observation representation."""
|
|
79
|
+
|
|
80
|
+
permalink: str
|
|
81
|
+
category: str
|
|
82
|
+
content: str
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class MemoryMetadata(BaseModel):
|
|
86
|
+
"""Simplified response metadata."""
|
|
87
|
+
|
|
88
|
+
uri: Optional[str] = None
|
|
89
|
+
types: Optional[List[SearchItemType]] = None
|
|
90
|
+
depth: int
|
|
91
|
+
timeframe: str
|
|
92
|
+
generated_at: datetime
|
|
93
|
+
total_results: int
|
|
94
|
+
total_relations: int
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class GraphContext(BaseModel):
|
|
98
|
+
"""Complete context response."""
|
|
99
|
+
|
|
100
|
+
# Direct matches
|
|
101
|
+
primary_results: List[EntitySummary | RelationSummary | ObservationSummary] = Field(
|
|
102
|
+
description="results directly matching URI"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Related entities
|
|
106
|
+
related_results: List[EntitySummary | RelationSummary | ObservationSummary] = Field(
|
|
107
|
+
description="related results"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Context metadata
|
|
111
|
+
metadata: MemoryMetadata
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Request schemas for interacting with the knowledge graph."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional, Annotated, Dict, Any
|
|
4
|
+
from annotated_types import MaxLen, MinLen
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from basic_memory.schemas.base import (
|
|
9
|
+
Observation,
|
|
10
|
+
Entity,
|
|
11
|
+
Relation,
|
|
12
|
+
PathId,
|
|
13
|
+
ObservationCategory,
|
|
14
|
+
EntityType,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SearchNodesRequest(BaseModel):
|
|
22
|
+
"""Search for entities in the knowledge graph.
|
|
23
|
+
|
|
24
|
+
The search looks across multiple fields:
|
|
25
|
+
- Entity title
|
|
26
|
+
- Entity types
|
|
27
|
+
- summary
|
|
28
|
+
- file content
|
|
29
|
+
- Observations
|
|
30
|
+
|
|
31
|
+
Features:
|
|
32
|
+
- Case-insensitive matching
|
|
33
|
+
- Partial word matches
|
|
34
|
+
- Returns full entity objects with relations
|
|
35
|
+
- Includes all matching entities
|
|
36
|
+
- If a category is specified, only entities with that category are returned
|
|
37
|
+
|
|
38
|
+
Example Queries:
|
|
39
|
+
- "memory" - Find entities related to memory systems
|
|
40
|
+
- "SQLite" - Find database-related components
|
|
41
|
+
- "test" - Find test-related entities
|
|
42
|
+
- "implementation" - Find concrete implementations
|
|
43
|
+
- "service" - Find service components
|
|
44
|
+
|
|
45
|
+
Note: Currently uses SQL ILIKE for matching. Wildcard (*) searches
|
|
46
|
+
and full-text search capabilities are planned for future versions.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
query: Annotated[str, MinLen(1), MaxLen(200)]
|
|
50
|
+
category: Optional[ObservationCategory] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class GetEntitiesRequest(BaseModel):
|
|
54
|
+
"""Retrieve specific entities by their IDs.
|
|
55
|
+
|
|
56
|
+
Used to load complete entity details including all observations
|
|
57
|
+
and relations. Particularly useful for following relations
|
|
58
|
+
discovered through search.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
permalinks: Annotated[List[PathId], MinLen(1)]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class CreateRelationsRequest(BaseModel):
|
|
65
|
+
relations: List[Relation]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## update
|
|
69
|
+
|
|
70
|
+
# TODO remove UpdateEntityRequest
|
|
71
|
+
class UpdateEntityRequest(BaseModel):
|
|
72
|
+
"""Request to update an existing entity."""
|
|
73
|
+
|
|
74
|
+
title: Optional[str] = None
|
|
75
|
+
entity_type: Optional[EntityType] = None
|
|
76
|
+
content: Optional[str] = None
|
|
77
|
+
entity_metadata: Optional[Dict[str, Any]] = None
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Response schemas for knowledge graph operations.
|
|
2
|
+
|
|
3
|
+
This module defines the response formats for all knowledge graph operations.
|
|
4
|
+
Each response includes complete information about the affected entities,
|
|
5
|
+
including IDs that can be used in subsequent operations.
|
|
6
|
+
|
|
7
|
+
Key Features:
|
|
8
|
+
1. Every created/updated object gets an ID
|
|
9
|
+
2. Relations are included with their parent entities
|
|
10
|
+
3. Responses include everything needed for next operations
|
|
11
|
+
4. Bulk operations return all affected items
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import List, Optional, Dict
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, ConfigDict, Field, AliasPath, AliasChoices
|
|
17
|
+
|
|
18
|
+
from basic_memory.schemas.base import Relation, PathId, EntityType, ContentType, Observation
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SQLAlchemyModel(BaseModel):
|
|
22
|
+
"""Base class for models that read from SQLAlchemy attributes.
|
|
23
|
+
|
|
24
|
+
This base class handles conversion of SQLAlchemy model attributes
|
|
25
|
+
to Pydantic model fields. All response models extend this to ensure
|
|
26
|
+
proper handling of database results.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
model_config = ConfigDict(from_attributes=True)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ObservationResponse(Observation, SQLAlchemyModel):
|
|
33
|
+
"""Schema for observation data returned from the service.
|
|
34
|
+
|
|
35
|
+
Each observation gets a unique ID that can be used for later
|
|
36
|
+
reference or deletion.
|
|
37
|
+
|
|
38
|
+
Example Response:
|
|
39
|
+
{
|
|
40
|
+
"category": "feature",
|
|
41
|
+
"content": "Added support for async operations",
|
|
42
|
+
"context": "Initial database design meeting"
|
|
43
|
+
}
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class RelationResponse(Relation, SQLAlchemyModel):
|
|
48
|
+
"""Response schema for relation operations.
|
|
49
|
+
|
|
50
|
+
Extends the base Relation model with a unique ID that can be
|
|
51
|
+
used for later modification or deletion.
|
|
52
|
+
|
|
53
|
+
Example Response:
|
|
54
|
+
{
|
|
55
|
+
"from_id": "test/memory_test",
|
|
56
|
+
"to_id": "component/memory-service",
|
|
57
|
+
"relation_type": "validates",
|
|
58
|
+
"context": "Comprehensive test suite"
|
|
59
|
+
}
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
from_id: PathId = Field(
|
|
63
|
+
# use the permalink from the associated Entity
|
|
64
|
+
# or the from_id value
|
|
65
|
+
validation_alias=AliasChoices(
|
|
66
|
+
AliasPath("from_entity", "permalink"),
|
|
67
|
+
"from_id",
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
to_id: Optional[PathId] = Field(
|
|
71
|
+
# use the permalink from the associated Entity
|
|
72
|
+
# or the to_id value
|
|
73
|
+
validation_alias=AliasChoices(
|
|
74
|
+
AliasPath("to_entity", "permalink"),
|
|
75
|
+
"to_id",
|
|
76
|
+
), default=None
|
|
77
|
+
)
|
|
78
|
+
to_name: Optional[PathId] = Field(
|
|
79
|
+
# use the permalink from the associated Entity
|
|
80
|
+
# or the to_id value
|
|
81
|
+
validation_alias=AliasChoices(
|
|
82
|
+
AliasPath("to_entity", "title"),
|
|
83
|
+
"to_name",
|
|
84
|
+
), default=None
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class EntityResponse(SQLAlchemyModel):
|
|
90
|
+
"""Complete entity data returned from the service.
|
|
91
|
+
|
|
92
|
+
This is the most comprehensive entity view, including:
|
|
93
|
+
1. Basic entity details (id, name, type)
|
|
94
|
+
2. All observations with their IDs
|
|
95
|
+
3. All relations with their IDs
|
|
96
|
+
4. Optional description
|
|
97
|
+
|
|
98
|
+
Example Response:
|
|
99
|
+
{
|
|
100
|
+
"permalink": "component/memory-service",
|
|
101
|
+
"file_path": "MemoryService",
|
|
102
|
+
"entity_type": "component",
|
|
103
|
+
"entity_metadata": {}
|
|
104
|
+
"content_type: "text/markdown"
|
|
105
|
+
"observations": [
|
|
106
|
+
{
|
|
107
|
+
"category": "feature",
|
|
108
|
+
"content": "Uses SQLite storage"
|
|
109
|
+
"context": "Initial design"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"category": "feature",
|
|
113
|
+
"content": "Implements async operations"
|
|
114
|
+
"context": "Initial design"
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"relations": [
|
|
118
|
+
{
|
|
119
|
+
"from_id": "test/memory-test",
|
|
120
|
+
"to_id": "component/memory-service",
|
|
121
|
+
"relation_type": "validates",
|
|
122
|
+
"context": "Main test suite"
|
|
123
|
+
}
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
permalink: PathId
|
|
129
|
+
title: str
|
|
130
|
+
file_path: str
|
|
131
|
+
entity_type: EntityType
|
|
132
|
+
entity_metadata: Optional[Dict] = None
|
|
133
|
+
content_type: ContentType
|
|
134
|
+
observations: List[ObservationResponse] = []
|
|
135
|
+
relations: List[RelationResponse] = []
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class EntityListResponse(SQLAlchemyModel):
|
|
139
|
+
"""Response for create_entities operation.
|
|
140
|
+
|
|
141
|
+
Returns complete information about entities returned from the service,
|
|
142
|
+
including their permalinks, observations,
|
|
143
|
+
and any established relations.
|
|
144
|
+
|
|
145
|
+
Example Response:
|
|
146
|
+
{
|
|
147
|
+
"entities": [
|
|
148
|
+
{
|
|
149
|
+
"permalink": "component/search_service",
|
|
150
|
+
"title": "SearchService",
|
|
151
|
+
"entity_type": "component",
|
|
152
|
+
"description": "Knowledge graph search",
|
|
153
|
+
"observations": [
|
|
154
|
+
{
|
|
155
|
+
"content": "Implements full-text search"
|
|
156
|
+
}
|
|
157
|
+
],
|
|
158
|
+
"relations": []
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"permalink": "document/api_docs",
|
|
162
|
+
"title": "API_Documentation",
|
|
163
|
+
"entity_type": "document",
|
|
164
|
+
"description": "API Reference",
|
|
165
|
+
"observations": [
|
|
166
|
+
{
|
|
167
|
+
"content": "Documents REST endpoints"
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
"relations": []
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
entities: List[EntityResponse]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class SearchNodesResponse(SQLAlchemyModel):
|
|
180
|
+
"""Response for search operation.
|
|
181
|
+
|
|
182
|
+
Returns matching entities with their complete information,
|
|
183
|
+
plus the original query for reference.
|
|
184
|
+
|
|
185
|
+
Example Response:
|
|
186
|
+
{
|
|
187
|
+
"matches": [
|
|
188
|
+
{
|
|
189
|
+
"permalink": "component/memory-service",
|
|
190
|
+
"title": "MemoryService",
|
|
191
|
+
"entity_type": "component",
|
|
192
|
+
"description": "Core service",
|
|
193
|
+
"observations": [...],
|
|
194
|
+
"relations": [...]
|
|
195
|
+
}
|
|
196
|
+
],
|
|
197
|
+
"query": "memory"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
Note: Each entity in matches includes full details
|
|
201
|
+
just like EntityResponse.
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
matches: List[EntityResponse]
|
|
205
|
+
query: str
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class DeleteEntitiesResponse(SQLAlchemyModel):
|
|
209
|
+
"""Response indicating successful entity deletion.
|
|
210
|
+
|
|
211
|
+
A simple boolean response confirming the delete operation
|
|
212
|
+
completed successfully.
|
|
213
|
+
|
|
214
|
+
Example Response:
|
|
215
|
+
{
|
|
216
|
+
"deleted": true
|
|
217
|
+
}
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
deleted: bool
|