graphiti-core 0.18.5__py3-none-any.whl → 0.18.7__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 graphiti-core might be problematic. Click here for more details.

graphiti_core/edges.py CHANGED
@@ -64,6 +64,21 @@ class Edge(BaseModel, ABC):
64
64
 
65
65
  return result
66
66
 
67
+ @classmethod
68
+ async def delete_by_uuids(cls, driver: GraphDriver, uuids: list[str]):
69
+ result = await driver.execute_query(
70
+ """
71
+ MATCH (n)-[e:MENTIONS|RELATES_TO|HAS_MEMBER]->(m)
72
+ WHERE e.uuid IN $uuids
73
+ DELETE e
74
+ """,
75
+ uuids=uuids,
76
+ )
77
+
78
+ logger.debug(f'Deleted Edges: {uuids}')
79
+
80
+ return result
81
+
67
82
  def __hash__(self):
68
83
  return hash(self.uuid)
69
84
 
graphiti_core/graphiti.py CHANGED
@@ -28,6 +28,7 @@ from graphiti_core.driver.driver import GraphDriver
28
28
  from graphiti_core.driver.neo4j_driver import Neo4jDriver
29
29
  from graphiti_core.edges import (
30
30
  CommunityEdge,
31
+ Edge,
31
32
  EntityEdge,
32
33
  EpisodicEdge,
33
34
  create_entity_edge_embeddings,
@@ -46,6 +47,7 @@ from graphiti_core.nodes import (
46
47
  EntityNode,
47
48
  EpisodeType,
48
49
  EpisodicNode,
50
+ Node,
49
51
  create_entity_node_embeddings,
50
52
  )
51
53
  from graphiti_core.search.search import SearchConfig, search
@@ -158,7 +160,7 @@ class Graphiti:
158
160
  If not set, the Graphiti default is used.
159
161
  ensure_ascii : bool, optional
160
162
  Whether to escape non-ASCII characters in JSON serialization for prompts. Defaults to False.
161
- Set to False to preserve non-ASCII characters (e.g., Korean, Japanese, Chinese) in their
163
+ Set as False to preserve non-ASCII characters (e.g., Korean, Japanese, Chinese) in their
162
164
  original form, making them readable in LLM logs and improving model understanding.
163
165
 
164
166
  Returns
@@ -1066,12 +1068,7 @@ class Graphiti:
1066
1068
  if record['episode_count'] == 1:
1067
1069
  nodes_to_delete.append(node)
1068
1070
 
1069
- await semaphore_gather(
1070
- *[node.delete(self.driver) for node in nodes_to_delete],
1071
- max_coroutines=self.max_coroutines,
1072
- )
1073
- await semaphore_gather(
1074
- *[edge.delete(self.driver) for edge in edges_to_delete],
1075
- max_coroutines=self.max_coroutines,
1076
- )
1071
+ await Node.delete_by_uuids(self.driver, [node.uuid for node in nodes_to_delete])
1072
+
1073
+ await Edge.delete_by_uuids(self.driver, [edge.uuid for edge in edges_to_delete])
1077
1074
  await episode.delete(self.driver)
graphiti_core/nodes.py CHANGED
@@ -142,6 +142,33 @@ class Node(BaseModel, ABC):
142
142
  batch_size=batch_size,
143
143
  )
144
144
 
145
+ @classmethod
146
+ async def delete_by_uuids(cls, driver: GraphDriver, uuids: list[str], batch_size: int = 100):
147
+ if driver.provider == GraphProvider.FALKORDB:
148
+ for label in ['Entity', 'Episodic', 'Community']:
149
+ await driver.execute_query(
150
+ f"""
151
+ MATCH (n:{label})
152
+ WHERE n.uuid IN $uuids
153
+ DETACH DELETE n
154
+ """,
155
+ uuids=uuids,
156
+ )
157
+ else:
158
+ async with driver.session() as session:
159
+ await session.run(
160
+ """
161
+ MATCH (n:Entity|Episodic|Community)
162
+ WHERE n.uuid IN $uuids
163
+ CALL {
164
+ WITH n
165
+ DETACH DELETE n
166
+ } IN TRANSACTIONS OF $batch_size ROWS
167
+ """,
168
+ uuids=uuids,
169
+ batch_size=batch_size,
170
+ )
171
+
145
172
  @classmethod
146
173
  async def get_by_uuid(cls, driver: GraphDriver, uuid: str): ...
147
174
 
@@ -177,31 +177,42 @@ async def edge_search(
177
177
  ) -> tuple[list[EntityEdge], list[float]]:
178
178
  if config is None:
179
179
  return [], []
180
- search_results: list[list[EntityEdge]] = list(
181
- await semaphore_gather(
182
- *[
183
- edge_fulltext_search(driver, query, search_filter, group_ids, 2 * limit),
184
- edge_similarity_search(
185
- driver,
186
- query_vector,
187
- None,
188
- None,
189
- search_filter,
190
- group_ids,
191
- 2 * limit,
192
- config.sim_min_score,
193
- ),
194
- edge_bfs_search(
195
- driver,
196
- bfs_origin_node_uuids,
197
- config.bfs_max_depth,
198
- search_filter,
199
- group_ids,
200
- 2 * limit,
201
- ),
202
- ]
180
+
181
+ # Build search tasks based on configured search methods
182
+ search_tasks = []
183
+ if EdgeSearchMethod.bm25 in config.search_methods:
184
+ search_tasks.append(
185
+ edge_fulltext_search(driver, query, search_filter, group_ids, 2 * limit)
203
186
  )
204
- )
187
+ if EdgeSearchMethod.cosine_similarity in config.search_methods:
188
+ search_tasks.append(
189
+ edge_similarity_search(
190
+ driver,
191
+ query_vector,
192
+ None,
193
+ None,
194
+ search_filter,
195
+ group_ids,
196
+ 2 * limit,
197
+ config.sim_min_score,
198
+ )
199
+ )
200
+ if EdgeSearchMethod.bfs in config.search_methods:
201
+ search_tasks.append(
202
+ edge_bfs_search(
203
+ driver,
204
+ bfs_origin_node_uuids,
205
+ config.bfs_max_depth,
206
+ search_filter,
207
+ group_ids,
208
+ 2 * limit,
209
+ )
210
+ )
211
+
212
+ # Execute only the configured search methods
213
+ search_results: list[list[EntityEdge]] = []
214
+ if search_tasks:
215
+ search_results = list(await semaphore_gather(*search_tasks))
205
216
 
206
217
  if EdgeSearchMethod.bfs in config.search_methods and bfs_origin_node_uuids is None:
207
218
  source_node_uuids = [edge.source_node_uuid for result in search_results for edge in result]
@@ -289,24 +300,35 @@ async def node_search(
289
300
  ) -> tuple[list[EntityNode], list[float]]:
290
301
  if config is None:
291
302
  return [], []
292
- search_results: list[list[EntityNode]] = list(
293
- await semaphore_gather(
294
- *[
295
- node_fulltext_search(driver, query, search_filter, group_ids, 2 * limit),
296
- node_similarity_search(
297
- driver, query_vector, search_filter, group_ids, 2 * limit, config.sim_min_score
298
- ),
299
- node_bfs_search(
300
- driver,
301
- bfs_origin_node_uuids,
302
- search_filter,
303
- config.bfs_max_depth,
304
- group_ids,
305
- 2 * limit,
306
- ),
307
- ]
303
+
304
+ # Build search tasks based on configured search methods
305
+ search_tasks = []
306
+ if NodeSearchMethod.bm25 in config.search_methods:
307
+ search_tasks.append(
308
+ node_fulltext_search(driver, query, search_filter, group_ids, 2 * limit)
308
309
  )
309
- )
310
+ if NodeSearchMethod.cosine_similarity in config.search_methods:
311
+ search_tasks.append(
312
+ node_similarity_search(
313
+ driver, query_vector, search_filter, group_ids, 2 * limit, config.sim_min_score
314
+ )
315
+ )
316
+ if NodeSearchMethod.bfs in config.search_methods:
317
+ search_tasks.append(
318
+ node_bfs_search(
319
+ driver,
320
+ bfs_origin_node_uuids,
321
+ search_filter,
322
+ config.bfs_max_depth,
323
+ group_ids,
324
+ 2 * limit,
325
+ )
326
+ )
327
+
328
+ # Execute only the configured search methods
329
+ search_results: list[list[EntityNode]] = []
330
+ if search_tasks:
331
+ search_results = list(await semaphore_gather(*search_tasks))
310
332
 
311
333
  if NodeSearchMethod.bfs in config.search_methods and bfs_origin_node_uuids is None:
312
334
  origin_node_uuids = [node.uuid for result in search_results for node in result]
@@ -28,6 +28,8 @@ class ComparisonOperator(Enum):
28
28
  less_than = '<'
29
29
  greater_than_equal = '>='
30
30
  less_than_equal = '<='
31
+ is_null = 'IS NULL'
32
+ is_not_null = 'IS NOT NULL'
31
33
 
32
34
 
33
35
  class DateFilter(BaseModel):
@@ -64,6 +66,19 @@ def node_search_filter_query_constructor(
64
66
  return filter_query, filter_params
65
67
 
66
68
 
69
+ def date_filter_query_constructor(
70
+ value_name: str, param_name: str, operator: ComparisonOperator
71
+ ) -> str:
72
+ query = '(' + value_name + ' '
73
+
74
+ if operator == ComparisonOperator.is_null or operator == ComparisonOperator.is_not_null:
75
+ query += operator.value + ')'
76
+ else:
77
+ query += operator.value + ' ' + param_name + ')'
78
+
79
+ return query
80
+
81
+
67
82
  def edge_search_filter_query_constructor(
68
83
  filters: SearchFilters,
69
84
  ) -> tuple[str, dict[str, Any]]:
@@ -85,10 +100,16 @@ def edge_search_filter_query_constructor(
85
100
  valid_at_filter = '\nAND ('
86
101
  for i, or_list in enumerate(filters.valid_at):
87
102
  for j, date_filter in enumerate(or_list):
88
- filter_params['valid_at_' + str(j)] = date_filter.date
103
+ if date_filter.comparison_operator not in [
104
+ ComparisonOperator.is_null,
105
+ ComparisonOperator.is_not_null,
106
+ ]:
107
+ filter_params['valid_at_' + str(j)] = date_filter.date
89
108
 
90
109
  and_filters = [
91
- '(e.valid_at ' + date_filter.comparison_operator.value + f' $valid_at_{j})'
110
+ date_filter_query_constructor(
111
+ 'e.valid_at', f'$valid_at_{j}', date_filter.comparison_operator
112
+ )
92
113
  for j, date_filter in enumerate(or_list)
93
114
  ]
94
115
  and_filter_query = ''
@@ -110,10 +131,16 @@ def edge_search_filter_query_constructor(
110
131
  invalid_at_filter = ' AND ('
111
132
  for i, or_list in enumerate(filters.invalid_at):
112
133
  for j, date_filter in enumerate(or_list):
113
- filter_params['invalid_at_' + str(j)] = date_filter.date
134
+ if date_filter.comparison_operator not in [
135
+ ComparisonOperator.is_null,
136
+ ComparisonOperator.is_not_null,
137
+ ]:
138
+ filter_params['invalid_at_' + str(j)] = date_filter.date
114
139
 
115
140
  and_filters = [
116
- '(e.invalid_at ' + date_filter.comparison_operator.value + f' $invalid_at_{j})'
141
+ date_filter_query_constructor(
142
+ 'e.invalid_at', f'$invalid_at_{j}', date_filter.comparison_operator
143
+ )
117
144
  for j, date_filter in enumerate(or_list)
118
145
  ]
119
146
  and_filter_query = ''
@@ -135,10 +162,16 @@ def edge_search_filter_query_constructor(
135
162
  created_at_filter = ' AND ('
136
163
  for i, or_list in enumerate(filters.created_at):
137
164
  for j, date_filter in enumerate(or_list):
138
- filter_params['created_at_' + str(j)] = date_filter.date
165
+ if date_filter.comparison_operator not in [
166
+ ComparisonOperator.is_null,
167
+ ComparisonOperator.is_not_null,
168
+ ]:
169
+ filter_params['created_at_' + str(j)] = date_filter.date
139
170
 
140
171
  and_filters = [
141
- '(e.created_at ' + date_filter.comparison_operator.value + f' $created_at_{j})'
172
+ date_filter_query_constructor(
173
+ 'e.created_at', f'$created_at_{j}', date_filter.comparison_operator
174
+ )
142
175
  for j, date_filter in enumerate(or_list)
143
176
  ]
144
177
  and_filter_query = ''
@@ -160,10 +193,16 @@ def edge_search_filter_query_constructor(
160
193
  expired_at_filter = ' AND ('
161
194
  for i, or_list in enumerate(filters.expired_at):
162
195
  for j, date_filter in enumerate(or_list):
163
- filter_params['expired_at_' + str(j)] = date_filter.date
196
+ if date_filter.comparison_operator not in [
197
+ ComparisonOperator.is_null,
198
+ ComparisonOperator.is_not_null,
199
+ ]:
200
+ filter_params['expired_at_' + str(j)] = date_filter.date
164
201
 
165
202
  and_filters = [
166
- '(e.expired_at ' + date_filter.comparison_operator.value + f' $expired_at_{j})'
203
+ date_filter_query_constructor(
204
+ 'e.expired_at', f'$expired_at_{j}', date_filter.comparison_operator
205
+ )
167
206
  for j, date_filter in enumerate(or_list)
168
207
  ]
169
208
  and_filter_query = ''
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphiti-core
3
- Version: 0.18.5
3
+ Version: 0.18.7
4
4
  Summary: A temporal graph building library
5
5
  Project-URL: Homepage, https://help.getzep.com/graphiti/graphiti/overview
6
6
  Project-URL: Repository, https://github.com/getzep/graphiti
@@ -1,11 +1,11 @@
1
1
  graphiti_core/__init__.py,sha256=e5SWFkRiaUwfprYIeIgVIh7JDedNiloZvd3roU-0aDY,55
2
- graphiti_core/edges.py,sha256=KpgCzHlNgEcTTbUE62RAXKKi2YbG_fIwilE_W1TARYI,14864
2
+ graphiti_core/edges.py,sha256=NWBOXRka7r_JMmtUF_6xUCMKyVDlEzRSOdGlz_aYucU,15260
3
3
  graphiti_core/errors.py,sha256=cH_v9TPgEPeQE6GFOHIg5TvejpUCBddGarMY2Whxbwc,2707
4
4
  graphiti_core/graph_queries.py,sha256=gXQvspJHpM5LRJ5HBJgr0Zw-AhHkqweCoq06wfyZ_bc,5407
5
- graphiti_core/graphiti.py,sha256=hm3UwlTEjpmAVhJ-iZf-aQuZJTz-BgaZZAgfnpNep1Q,40953
5
+ graphiti_core/graphiti.py,sha256=R6VrGfPcvgPhaZZsfxb_EXM6bSYLWW5iALggfwkU2oA,40834
6
6
  graphiti_core/graphiti_types.py,sha256=C_p2XwScQlCzo7ets097TrSLs9ATxPZQ4WCsxDS7QHc,1066
7
7
  graphiti_core/helpers.py,sha256=oKcOQE_bvsdhBpPr1Ia2tylBq1svj3X1oBMSR7qdo00,5331
8
- graphiti_core/nodes.py,sha256=7K5fiEG8iTgB0rqk_IcC9K--5ycTwmvSvWEwZU1E_zU,18123
8
+ graphiti_core/nodes.py,sha256=aGCAoKWYPEr0wiC6UoxpIs7GxA4MXxugHCZtdfV3iwk,19128
9
9
  graphiti_core/py.typed,sha256=vlmmzQOt7bmeQl9L3XJP4W6Ry0iiELepnOrinKz5KQg,79
10
10
  graphiti_core/cross_encoder/__init__.py,sha256=hry59vz21x-AtGZ0MJ7ugw0HTwJkXiddpp_Yqnwsen0,723
11
11
  graphiti_core/cross_encoder/bge_reranker_client.py,sha256=y3TfFxZh0Yvj6HUShmfUm6MC7OPXwWUlv1Qe5HF3S3I,1797
@@ -52,10 +52,10 @@ graphiti_core/prompts/models.py,sha256=NgxdbPHJpBEcpbXovKyScgpBc73Q-GIW-CBDlBtDj
52
52
  graphiti_core/prompts/prompt_helpers.py,sha256=gMDDNqBpxcnTO9psJQm7QU7M6OQgRumFq4oGYiycrfM,795
53
53
  graphiti_core/prompts/summarize_nodes.py,sha256=tn6LPEv_nNFLjKuT_FB_st7TAIYOEUOg9QR5YG7PpMA,4437
54
54
  graphiti_core/search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
- graphiti_core/search/search.py,sha256=u-kTmSu3VlRHYlQhuYsbwDQ-AKKCp3BZ9JZNRv3ttVY,16720
55
+ graphiti_core/search/search.py,sha256=TnBtwqL0dWADBZqQT_ymlJm2qA2Llj-0SwS8vS6nH-0,17464
56
56
  graphiti_core/search/search_config.py,sha256=v_rUHsu1yo5OuPfEm21lSuXexQs-o8qYwSSemW2QWhU,4165
57
57
  graphiti_core/search/search_config_recipes.py,sha256=4GquRphHhJlpXQhAZOySYnCzBWYoTwxlJj44eTOavZQ,7443
58
- graphiti_core/search/search_filters.py,sha256=f7rddW6sPIRRCVoBOS9nrOsjj-6ld03EGEQbTB2lzLg,6390
58
+ graphiti_core/search/search_filters.py,sha256=BkkVpweN5U_ld5n2GyQrljwGw4QwbFphE7FT0jpTys8,7772
59
59
  graphiti_core/search/search_helpers.py,sha256=wj3ARlCNnZixNNntgCdAqzGoE4de4lW3r4rSG-3WyGw,2877
60
60
  graphiti_core/search/search_utils.py,sha256=D6J93x0ol4Kd_zVo-dX0MRG0BZjLvG-mCdi-vjjDTfE,32104
61
61
  graphiti_core/telemetry/__init__.py,sha256=5kALLDlU9bb2v19CdN7qVANsJWyfnL9E60J6FFgzm3o,226
@@ -71,7 +71,7 @@ graphiti_core/utils/maintenance/node_operations.py,sha256=SU-u69HrVJNvDsYC6kylcq
71
71
  graphiti_core/utils/maintenance/temporal_operations.py,sha256=IIaVtShpVkOYe6haxz3a1x3v54-MzaEXG8VsxFUNeoY,3582
72
72
  graphiti_core/utils/maintenance/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
73
  graphiti_core/utils/ontology_utils/entity_types_utils.py,sha256=4eVgxLWY6Q8k9cRJ5pW59IYF--U4nXZsZIGOVb_yHfQ,1285
74
- graphiti_core-0.18.5.dist-info/METADATA,sha256=S2N9HqHBbA74Rpck6iN7uV7ZzMFBp5i9yNDcZjAvi-c,24613
75
- graphiti_core-0.18.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
76
- graphiti_core-0.18.5.dist-info/licenses/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
77
- graphiti_core-0.18.5.dist-info/RECORD,,
74
+ graphiti_core-0.18.7.dist-info/METADATA,sha256=ycXECklR_Mxfdbu_zL3U3zsZqf01zt8eZfrjaMqJPLk,24613
75
+ graphiti_core-0.18.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
76
+ graphiti_core-0.18.7.dist-info/licenses/LICENSE,sha256=KCUwCyDXuVEgmDWkozHyniRyWjnWUWjkuDHfU6o3JlA,11325
77
+ graphiti_core-0.18.7.dist-info/RECORD,,