15
15
import logging
16
16
import re
17
17
import time
18
- from typing import TYPE_CHECKING , Any , Dict , List , Tuple
18
+ from typing import TYPE_CHECKING , Any , Dict , List , Optional , Tuple
19
19
20
20
from camel .storages .graph_storages .base import BaseGraphStorage
21
21
from camel .storages .graph_storages .graph_element import (
@@ -203,46 +203,62 @@ def add_graph_elements(
203
203
def ensure_edge_type_exists (
204
204
self ,
205
205
edge_type : str ,
206
+ time_label : Optional [str ] = None ,
206
207
) -> None :
207
208
r"""Ensures that a specified edge type exists in the NebulaGraph
208
209
database. If the edge type already exists, this method does nothing.
209
210
210
211
Args:
211
212
edge_type (str): The name of the edge type to be created.
213
+ time_label (str, optional): A specific timestamp to set as the
214
+ default value for the time label property. If not
215
+ provided, no timestamp will be added. (default: :obj:`None`)
212
216
213
217
Raises:
214
218
Exception: If the edge type creation fails after multiple retry
215
219
attempts, an exception is raised with the error message.
216
220
"""
217
- create_edge_stmt = f'CREATE EDGE IF NOT EXISTS { edge_type } ()'
221
+ create_edge_stmt = f"CREATE EDGE IF NOT EXISTS { edge_type } ()"
222
+ if time_label is not None :
223
+ time_label = self ._validate_time_label (time_label )
224
+ create_edge_stmt = f"""CREATE EDGE IF NOT EXISTS { edge_type }
225
+ (time_label DATETIME DEFAULT { time_label } )"""
218
226
219
227
for attempt in range (MAX_RETRIES ):
220
228
res = self .query (create_edge_stmt )
221
229
if res .is_succeeded ():
222
- return # Tag creation succeeded, exit the method
230
+ return # Edge type creation succeeded
223
231
224
232
if attempt < MAX_RETRIES - 1 :
225
233
time .sleep (RETRY_DELAY )
226
234
else :
227
235
# Final attempt failed, raise an exception
228
236
raise Exception (
229
- f"Failed to create tag `{ edge_type } ` after "
237
+ f"Failed to create edge type `{ edge_type } ` after "
230
238
f"{ MAX_RETRIES } attempts: { res .error_msg ()} "
231
239
)
232
240
233
- def ensure_tag_exists (self , tag_name : str ) -> None :
241
+ def ensure_tag_exists (
242
+ self , tag_name : str , time_label : Optional [str ] = None
243
+ ) -> None :
234
244
r"""Ensures a tag is created in the NebulaGraph database. If the tag
235
245
already exists, it does nothing.
236
246
237
247
Args:
238
248
tag_name (str): The name of the tag to be created.
249
+ time_label (str, optional): A specific timestamp to set as the
250
+ default value for the time label property. If not provided,
251
+ no timestamp will be added. (default: :obj:`None`)
239
252
240
253
Raises:
241
254
Exception: If the tag creation fails after retries, an exception
242
255
is raised with the error message.
243
256
"""
244
-
245
- create_tag_stmt = f'CREATE TAG IF NOT EXISTS { tag_name } ()'
257
+ create_tag_stmt = f"CREATE TAG IF NOT EXISTS { tag_name } ()"
258
+ if time_label is not None :
259
+ time_label = self ._validate_time_label (time_label )
260
+ create_tag_stmt = f"""CREATE TAG IF NOT EXISTS { tag_name }
261
+ (time_label DATETIME DEFAULT { time_label } )"""
246
262
247
263
for attempt in range (MAX_RETRIES ):
248
264
res = self .query (create_tag_stmt )
@@ -262,27 +278,39 @@ def add_node(
262
278
self ,
263
279
node_id : str ,
264
280
tag_name : str ,
281
+ time_label : Optional [str ] = None ,
265
282
) -> None :
266
283
r"""Add a node with the specified tag and properties.
267
284
268
285
Args:
269
286
node_id (str): The ID of the node.
270
287
tag_name (str): The tag name of the node.
288
+ time_label (str, optional): A specific timestamp to set for
289
+ the node's time label property. If not provided, no timestamp
290
+ will be added. (default: :obj:`None`)
271
291
"""
272
292
node_id = re .sub (r'[^a-zA-Z0-9\u4e00-\u9fa5]' , '' , node_id )
273
293
tag_name = re .sub (r'[^a-zA-Z0-9\u4e00-\u9fa5]' , '' , tag_name )
274
294
275
- self .ensure_tag_exists (tag_name )
295
+ self .ensure_tag_exists (tag_name , time_label )
276
296
277
- # Insert node without properties
278
- insert_stmt = (
279
- f'INSERT VERTEX IF NOT EXISTS { tag_name } () VALUES "{ node_id } ":()'
280
- )
297
+ # Insert node with or without time_label property
298
+ if time_label is not None :
299
+ time_label = self ._validate_time_label (time_label )
300
+ insert_stmt = (
301
+ f'INSERT VERTEX IF NOT EXISTS { tag_name } (time_label) VALUES '
302
+ f'"{ node_id } ":("{ time_label } ")'
303
+ )
304
+ else :
305
+ insert_stmt = (
306
+ f'INSERT VERTEX IF NOT EXISTS { tag_name } () VALUES '
307
+ f'"{ node_id } ":()'
308
+ )
281
309
282
310
for attempt in range (MAX_RETRIES ):
283
311
res = self .query (insert_stmt )
284
312
if res .is_succeeded ():
285
- return # Tag creation succeeded, exit the method
313
+ return # Node creation succeeded, exit the method
286
314
287
315
if attempt < MAX_RETRIES - 1 :
288
316
time .sleep (RETRY_DELAY )
@@ -348,7 +376,7 @@ def refresh_schema(self) -> None:
348
376
@property
349
377
def get_structured_schema (self ) -> Dict [str , Any ]:
350
378
r"""Generates a structured schema consisting of node and relationship
351
- properties, relationships, and metadata.
379
+ properties, relationships, and metadata, including timestamps .
352
380
353
381
Returns:
354
382
Dict[str, Any]: A dictionary representing the structured schema.
@@ -419,6 +447,7 @@ def add_triplet(
419
447
subj : str ,
420
448
obj : str ,
421
449
rel : str ,
450
+ time_label : Optional [str ] = None ,
422
451
) -> None :
423
452
r"""Adds a relationship (triplet) between two entities in the Nebula
424
453
Graph database.
@@ -427,28 +456,44 @@ def add_triplet(
427
456
subj (str): The identifier for the subject entity.
428
457
obj (str): The identifier for the object entity.
429
458
rel (str): The relationship between the subject and object.
459
+ time_label (str, optional): A specific timestamp to set for the
460
+ time label property of the relationship. If not provided,
461
+ no timestamp will be added. (default: :obj:`None`)
462
+
463
+ Raises:
464
+ ValueError: If the time_label format is invalid.
465
+ Exception: If creating the relationship fails.
430
466
"""
431
467
subj = re .sub (r'[^a-zA-Z0-9\u4e00-\u9fa5]' , '' , subj )
432
468
obj = re .sub (r'[^a-zA-Z0-9\u4e00-\u9fa5]' , '' , obj )
433
469
rel = re .sub (r'[^a-zA-Z0-9\u4e00-\u9fa5]' , '' , rel )
434
470
435
471
self .ensure_tag_exists (subj )
436
472
self .ensure_tag_exists (obj )
437
- self .ensure_edge_type_exists (rel )
473
+ self .ensure_edge_type_exists (rel , time_label )
438
474
self .add_node (node_id = subj , tag_name = subj )
439
475
self .add_node (node_id = obj , tag_name = obj )
440
476
441
- # Avoid latenicy
477
+ # Avoid latency
442
478
time .sleep (1 )
443
479
444
- insert_stmt = (
445
- f'INSERT EDGE IF NOT EXISTS { rel } () VALUES "{ subj } "->"{ obj } ":();'
446
- )
480
+ # Create edge with or without time_label property
481
+ if time_label is not None :
482
+ time_label = self ._validate_time_label (time_label )
483
+ insert_stmt = (
484
+ f'INSERT EDGE IF NOT EXISTS { rel } (time_label) VALUES '
485
+ f'"{ subj } "->"{ obj } ":("{ time_label } ")'
486
+ )
487
+ else :
488
+ insert_stmt = (
489
+ f'INSERT EDGE IF NOT EXISTS { rel } () VALUES '
490
+ f'"{ subj } "->"{ obj } ":()'
491
+ )
447
492
448
493
res = self .query (insert_stmt )
449
494
if not res .is_succeeded ():
450
495
raise Exception (
451
- f'create relationship `] { subj } ` -> `{ obj } `'
496
+ f'create relationship `{ subj } ` -> `{ obj } `'
452
497
+ f'failed: { res .error_msg ()} '
453
498
)
454
499
@@ -568,3 +613,27 @@ def get_relationship_properties(
568
613
)
569
614
570
615
return rel_schema_props , rel_structure_props
616
+
617
+ def _validate_time_label (self , time_label : str ) -> str :
618
+ r"""Validates the format of a time label string.
619
+
620
+ Args:
621
+ time_label (str): The time label string to validate.
622
+ Should be in format 'YYYY-MM-DDThh:mm:ss'.
623
+
624
+ Returns:
625
+ str: The validated time label.
626
+
627
+ Raises:
628
+ ValueError: If the time label format is invalid.
629
+ """
630
+ try :
631
+ # Check if the format matches YYYY-MM-DDThh:mm:ss
632
+ pattern = r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$'
633
+ if not re .match (pattern , time_label ):
634
+ raise ValueError (
635
+ "Time label must be in format 'YYYY-MM-DDThh:mm:ss'"
636
+ )
637
+ return time_label
638
+ except Exception as e :
639
+ raise ValueError (f"Invalid time label format: { e !s} " )
0 commit comments