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.

Files changed (73) hide show
  1. basic_memory/__init__.py +3 -0
  2. basic_memory/api/__init__.py +4 -0
  3. basic_memory/api/app.py +42 -0
  4. basic_memory/api/routers/__init__.py +8 -0
  5. basic_memory/api/routers/knowledge_router.py +168 -0
  6. basic_memory/api/routers/memory_router.py +123 -0
  7. basic_memory/api/routers/resource_router.py +34 -0
  8. basic_memory/api/routers/search_router.py +34 -0
  9. basic_memory/cli/__init__.py +1 -0
  10. basic_memory/cli/app.py +4 -0
  11. basic_memory/cli/commands/__init__.py +9 -0
  12. basic_memory/cli/commands/init.py +38 -0
  13. basic_memory/cli/commands/status.py +152 -0
  14. basic_memory/cli/commands/sync.py +254 -0
  15. basic_memory/cli/main.py +48 -0
  16. basic_memory/config.py +53 -0
  17. basic_memory/db.py +135 -0
  18. basic_memory/deps.py +182 -0
  19. basic_memory/file_utils.py +248 -0
  20. basic_memory/markdown/__init__.py +19 -0
  21. basic_memory/markdown/entity_parser.py +137 -0
  22. basic_memory/markdown/markdown_processor.py +153 -0
  23. basic_memory/markdown/plugins.py +236 -0
  24. basic_memory/markdown/schemas.py +73 -0
  25. basic_memory/markdown/utils.py +144 -0
  26. basic_memory/mcp/__init__.py +1 -0
  27. basic_memory/mcp/async_client.py +10 -0
  28. basic_memory/mcp/main.py +21 -0
  29. basic_memory/mcp/server.py +39 -0
  30. basic_memory/mcp/tools/__init__.py +34 -0
  31. basic_memory/mcp/tools/ai_edit.py +84 -0
  32. basic_memory/mcp/tools/knowledge.py +56 -0
  33. basic_memory/mcp/tools/memory.py +142 -0
  34. basic_memory/mcp/tools/notes.py +122 -0
  35. basic_memory/mcp/tools/search.py +28 -0
  36. basic_memory/mcp/tools/utils.py +154 -0
  37. basic_memory/models/__init__.py +12 -0
  38. basic_memory/models/base.py +9 -0
  39. basic_memory/models/knowledge.py +204 -0
  40. basic_memory/models/search.py +34 -0
  41. basic_memory/repository/__init__.py +7 -0
  42. basic_memory/repository/entity_repository.py +156 -0
  43. basic_memory/repository/observation_repository.py +40 -0
  44. basic_memory/repository/relation_repository.py +78 -0
  45. basic_memory/repository/repository.py +303 -0
  46. basic_memory/repository/search_repository.py +259 -0
  47. basic_memory/schemas/__init__.py +73 -0
  48. basic_memory/schemas/base.py +216 -0
  49. basic_memory/schemas/delete.py +38 -0
  50. basic_memory/schemas/discovery.py +25 -0
  51. basic_memory/schemas/memory.py +111 -0
  52. basic_memory/schemas/request.py +77 -0
  53. basic_memory/schemas/response.py +220 -0
  54. basic_memory/schemas/search.py +117 -0
  55. basic_memory/services/__init__.py +11 -0
  56. basic_memory/services/context_service.py +274 -0
  57. basic_memory/services/entity_service.py +281 -0
  58. basic_memory/services/exceptions.py +15 -0
  59. basic_memory/services/file_service.py +213 -0
  60. basic_memory/services/link_resolver.py +126 -0
  61. basic_memory/services/search_service.py +218 -0
  62. basic_memory/services/service.py +36 -0
  63. basic_memory/sync/__init__.py +5 -0
  64. basic_memory/sync/file_change_scanner.py +162 -0
  65. basic_memory/sync/sync_service.py +140 -0
  66. basic_memory/sync/utils.py +66 -0
  67. basic_memory/sync/watch_service.py +197 -0
  68. basic_memory/utils.py +78 -0
  69. basic_memory-0.0.0.dist-info/METADATA +71 -0
  70. basic_memory-0.0.0.dist-info/RECORD +73 -0
  71. basic_memory-0.0.0.dist-info/WHEEL +4 -0
  72. basic_memory-0.0.0.dist-info/entry_points.txt +2 -0
  73. 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