lionagi 0.1.0__py3-none-any.whl → 0.1.1__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.
Files changed (83) hide show
  1. lionagi/core/agent/base_agent.py +2 -3
  2. lionagi/core/branch/base.py +1 -1
  3. lionagi/core/branch/branch.py +2 -1
  4. lionagi/core/branch/flow_mixin.py +1 -1
  5. lionagi/core/branch/util.py +1 -1
  6. lionagi/core/execute/base_executor.py +1 -4
  7. lionagi/core/execute/branch_executor.py +66 -3
  8. lionagi/core/execute/instruction_map_executor.py +48 -0
  9. lionagi/core/execute/neo4j_executor.py +381 -0
  10. lionagi/core/execute/structure_executor.py +99 -3
  11. lionagi/core/flow/monoflow/ReAct.py +18 -18
  12. lionagi/core/flow/monoflow/chat_mixin.py +1 -1
  13. lionagi/core/flow/monoflow/followup.py +11 -12
  14. lionagi/core/flow/polyflow/__init__.py +1 -1
  15. lionagi/core/generic/component.py +0 -2
  16. lionagi/core/generic/condition.py +1 -1
  17. lionagi/core/generic/edge.py +52 -0
  18. lionagi/core/mail/mail_manager.py +3 -2
  19. lionagi/core/session/session.py +1 -1
  20. lionagi/experimental/__init__.py +0 -0
  21. lionagi/experimental/directive/__init__.py +0 -0
  22. lionagi/experimental/directive/evaluator/__init__.py +0 -0
  23. lionagi/experimental/directive/evaluator/ast_evaluator.py +115 -0
  24. lionagi/experimental/directive/evaluator/base_evaluator.py +202 -0
  25. lionagi/experimental/directive/evaluator/sandbox_.py +14 -0
  26. lionagi/experimental/directive/evaluator/script_engine.py +83 -0
  27. lionagi/experimental/directive/parser/__init__.py +0 -0
  28. lionagi/experimental/directive/parser/base_parser.py +215 -0
  29. lionagi/experimental/directive/schema.py +36 -0
  30. lionagi/experimental/directive/template_/__init__.py +0 -0
  31. lionagi/experimental/directive/template_/base_template.py +63 -0
  32. lionagi/experimental/tool/__init__.py +0 -0
  33. lionagi/experimental/tool/function_calling.py +43 -0
  34. lionagi/experimental/tool/manual.py +66 -0
  35. lionagi/experimental/tool/schema.py +59 -0
  36. lionagi/experimental/tool/tool_manager.py +138 -0
  37. lionagi/experimental/tool/util.py +16 -0
  38. lionagi/experimental/work/__init__.py +0 -0
  39. lionagi/experimental/work/_logger.py +25 -0
  40. lionagi/experimental/work/exchange.py +0 -0
  41. lionagi/experimental/work/schema.py +30 -0
  42. lionagi/experimental/work/tests.py +72 -0
  43. lionagi/experimental/work/util.py +0 -0
  44. lionagi/experimental/work/work_function.py +89 -0
  45. lionagi/experimental/work/worker.py +12 -0
  46. lionagi/integrations/bridge/autogen_/__init__.py +0 -0
  47. lionagi/integrations/bridge/autogen_/autogen_.py +124 -0
  48. lionagi/integrations/bridge/llamaindex_/get_index.py +294 -0
  49. lionagi/integrations/bridge/llamaindex_/llama_pack.py +227 -0
  50. lionagi/integrations/bridge/transformers_/__init__.py +0 -0
  51. lionagi/integrations/bridge/transformers_/install_.py +36 -0
  52. lionagi/integrations/config/oai_configs.py +1 -1
  53. lionagi/integrations/config/ollama_configs.py +1 -1
  54. lionagi/integrations/config/openrouter_configs.py +1 -1
  55. lionagi/integrations/storage/__init__.py +3 -0
  56. lionagi/integrations/storage/neo4j.py +673 -0
  57. lionagi/integrations/storage/storage_util.py +289 -0
  58. lionagi/integrations/storage/to_csv.py +63 -0
  59. lionagi/integrations/storage/to_excel.py +67 -0
  60. lionagi/libs/ln_knowledge_graph.py +405 -0
  61. lionagi/libs/ln_queue.py +101 -0
  62. lionagi/libs/ln_tokenizer.py +57 -0
  63. lionagi/libs/sys_util.py +1 -1
  64. lionagi/lions/__init__.py +0 -0
  65. lionagi/lions/coder/__init__.py +0 -0
  66. lionagi/lions/coder/add_feature.py +20 -0
  67. lionagi/lions/coder/base_prompts.py +22 -0
  68. lionagi/lions/coder/coder.py +121 -0
  69. lionagi/lions/coder/util.py +91 -0
  70. lionagi/lions/researcher/__init__.py +0 -0
  71. lionagi/lions/researcher/data_source/__init__.py +0 -0
  72. lionagi/lions/researcher/data_source/finhub_.py +191 -0
  73. lionagi/lions/researcher/data_source/google_.py +199 -0
  74. lionagi/lions/researcher/data_source/wiki_.py +96 -0
  75. lionagi/lions/researcher/data_source/yfinance_.py +21 -0
  76. lionagi/tests/libs/test_queue.py +67 -0
  77. lionagi/tests/test_core/test_branch.py +0 -1
  78. lionagi/version.py +1 -1
  79. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/METADATA +1 -1
  80. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/RECORD +83 -29
  81. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/LICENSE +0 -0
  82. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/WHEEL +0 -0
  83. {lionagi-0.1.0.dist-info → lionagi-0.1.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,673 @@
1
+ from neo4j import AsyncGraphDatabase
2
+
3
+ from lionagi.integrations.storage.storage_util import output_node_list, output_edge_list
4
+
5
+
6
+ class Neo4j:
7
+ """
8
+ Manages interactions with a Neo4j graph database, facilitating the creation, retrieval, and management
9
+ of graph nodes and relationships asynchronously.
10
+
11
+ Provides methods to add various types of nodes and relationships, query the graph based on specific criteria,
12
+ and enforce database constraints to ensure data integrity.
13
+
14
+ Attributes:
15
+ database (str): The name of the database to connect to.
16
+ driver (neo4j.AsyncGraphDatabase.driver): The Neo4j driver for asynchronous database operations.
17
+ """
18
+
19
+ def __init__(self, uri, user, password, database):
20
+ """
21
+ Initializes the Neo4j database connection using provided credentials and database information.
22
+
23
+ Args:
24
+ uri (str): The URI for the Neo4j database.
25
+ user (str): The username for database authentication.
26
+ password (str): The password for database authentication.
27
+ database (str): The name of the database to use.
28
+ """
29
+ self.database = database
30
+ self.driver = AsyncGraphDatabase.driver(uri, auth=(user, password))
31
+
32
+ # ---------------------to_neo4j---------------------------------
33
+ @staticmethod
34
+ async def add_structure_node(tx, node, name):
35
+ """
36
+ Asynchronously adds a structure node to the graph.
37
+
38
+ Args:
39
+ tx: The Neo4j transaction.
40
+ node (dict): The properties of the node to be added, including 'id' and 'timestamp'.
41
+ name (str): The name of the structure, which is set on the node.
42
+ """
43
+ query = """
44
+ MERGE (n:Structure:LionNode {id:$id})
45
+ SET n.timestamp = $timestamp
46
+ SET n.name = $name
47
+ """
48
+ await tx.run(query, id=node["id"], timestamp=node["timestamp"], name=name)
49
+ # heads=node['head_nodes'],
50
+ # nodes=node['nodes'],
51
+ # edges=node['edges'])
52
+
53
+ @staticmethod
54
+ async def add_system_node(tx, node):
55
+ """
56
+ Asynchronously adds a system node to the graph.
57
+
58
+ Args:
59
+ tx: The Neo4j transaction.
60
+ node (dict): The properties of the system node including 'id', 'timestamp', 'content', 'sender', and 'recipient'.
61
+ """
62
+ query = """
63
+ MERGE (n:System:LionNode {id: $id})
64
+ SET n.timestamp = $timestamp
65
+ SET n.content = $content
66
+ SET n.sender = $sender
67
+ SET n.recipient = $recipient
68
+ """
69
+ await tx.run(
70
+ query,
71
+ id=node["id"],
72
+ timestamp=node["timestamp"],
73
+ content=node["content"],
74
+ sender=node["sender"],
75
+ recipient=node["recipient"],
76
+ )
77
+
78
+ @staticmethod
79
+ async def add_instruction_node(tx, node):
80
+ """
81
+ Asynchronously adds an instruction node to the graph.
82
+
83
+ Args:
84
+ tx: The Neo4j transaction.
85
+ node (dict): The properties of the instruction node including 'id', 'timestamp', 'content', 'sender', and 'recipient'.
86
+ """
87
+ query = """
88
+ MERGE (n:Instruction:LionNode {id: $id})
89
+ SET n.timestamp = $timestamp
90
+ SET n.content = $content
91
+ SET n.sender = $sender
92
+ SET n.recipient = $recipient
93
+ """
94
+ await tx.run(
95
+ query,
96
+ id=node["id"],
97
+ timestamp=node["timestamp"],
98
+ content=node["content"],
99
+ sender=node["sender"],
100
+ recipient=node["recipient"],
101
+ )
102
+
103
+ # TODO: tool.manual
104
+ @staticmethod
105
+ async def add_tool_node(tx, node):
106
+ """
107
+ Asynchronously adds a tool node to the graph.
108
+
109
+ Args:
110
+ tx: The Neo4j transaction.
111
+ node (dict): The properties of the tool node including 'id', 'timestamp', 'function', and 'parser'.
112
+ """
113
+ query = """
114
+ MERGE (n:Tool:LionNode {id: $id})
115
+ SET n.timestamp = $timestamp
116
+ SET n.function = $function
117
+ SET n.parser = $parser
118
+ """
119
+ await tx.run(
120
+ query,
121
+ id=node["id"],
122
+ timestamp=node["timestamp"],
123
+ function=node["function"],
124
+ parser=node["parser"],
125
+ )
126
+
127
+ @staticmethod
128
+ async def add_actionSelection_node(tx, node):
129
+ """
130
+ Asynchronously adds an action selection node to the graph.
131
+
132
+ Args:
133
+ tx: The Neo4j transaction.
134
+ node (dict): The properties of the action selection node including 'id', 'action', and 'actionKwargs'.
135
+ """
136
+ query = """
137
+ MERGE (n:ActionSelection:LionNode {id: $id})
138
+ SET n.action = $action
139
+ SET n.actionKwargs = $actionKwargs
140
+ """
141
+ await tx.run(
142
+ query,
143
+ id=node["id"],
144
+ action=node["action"],
145
+ actionKwargs=node["action_kwargs"],
146
+ )
147
+
148
+ @staticmethod
149
+ async def add_baseAgent_node(tx, node):
150
+ """
151
+ Asynchronously adds an agent node to the graph.
152
+
153
+ Args:
154
+ tx: The Neo4j transaction.
155
+ node (dict): The properties of the agent node including 'id', 'timestamp', 'structureId', and 'outputParser'.
156
+ """
157
+ query = """
158
+ MERGE (n:Agent:LionNode {id:$id})
159
+ SET n.timestamp = $timestamp
160
+ SET n.structureId = $structureId
161
+ SET n.outputParser = $outputParser
162
+ """
163
+ await tx.run(
164
+ query,
165
+ id=node["id"],
166
+ timestamp=node["timestamp"],
167
+ structureId=node["structure_id"],
168
+ outputParser=node["output_parser"],
169
+ )
170
+
171
+ @staticmethod
172
+ async def add_forward_edge(tx, edge):
173
+ """
174
+ Asynchronously adds a forward relationship between two nodes in the graph.
175
+
176
+ Args:
177
+ tx: The Neo4j transaction.
178
+ edge (dict): The properties of the edge including 'id', 'timestamp', 'head', 'tail', 'label', and 'condition'.
179
+ """
180
+ query = """
181
+ MATCH (m:LionNode) WHERE m.id = $head
182
+ MATCH (n:LionNode) WHERE n.id = $tail
183
+ MERGE (m)-[r:FORWARD]->(n)
184
+ SET r.id = $id
185
+ SET r.timestamp = $timestamp
186
+ SET r.label = $label
187
+ SET r.condition = $condition
188
+ """
189
+ await tx.run(
190
+ query,
191
+ id=edge["id"],
192
+ timestamp=edge["timestamp"],
193
+ head=edge["head"],
194
+ tail=edge["tail"],
195
+ label=edge["label"],
196
+ condition=edge["condition"],
197
+ )
198
+
199
+ @staticmethod
200
+ async def add_bundle_edge(tx, edge):
201
+ """
202
+ Asynchronously adds a bundle relationship between two nodes in the graph.
203
+
204
+ Args:
205
+ tx: The Neo4j transaction.
206
+ edge (dict): The properties of the edge including 'id', 'timestamp', 'head', 'tail', 'label', and 'condition'.
207
+ """
208
+ query = """
209
+ MATCH (m:LionNode) WHERE m.id = $head
210
+ MATCH (n:LionNode) WHERE n.id = $tail
211
+ MERGE (m)-[r:BUNDLE]->(n)
212
+ SET r.id = $id
213
+ SET r.timestamp = $timestamp
214
+ SET r.label = $label
215
+ SET r.condition = $condition
216
+ """
217
+ await tx.run(
218
+ query,
219
+ id=edge["id"],
220
+ timestamp=edge["timestamp"],
221
+ head=edge["head"],
222
+ tail=edge["tail"],
223
+ label=edge["label"],
224
+ condition=edge["condition"],
225
+ )
226
+
227
+ @staticmethod
228
+ async def add_head_edge(tx, structure):
229
+ """
230
+ Asynchronously adds head relationships from a structure node to its head nodes.
231
+
232
+ Args:
233
+ tx: The Neo4j transaction.
234
+ structure: The structure node from which head relationships are established.
235
+ """
236
+ for head in structure.get_heads():
237
+ head_id = head.id_
238
+ query = """
239
+ MATCH (m:Structure) WHERE m.id = $structureId
240
+ MATCH (n:LionNode) WHERE n.id = $headId
241
+ MERGE (m)-[:HEAD]->(n)
242
+ """
243
+ await tx.run(query, structureId=structure.id_, headId=head_id)
244
+
245
+ @staticmethod
246
+ async def add_single_condition_cls(tx, condCls):
247
+ """
248
+ Asynchronously adds a condition class node to the graph.
249
+
250
+ Args:
251
+ tx: The Neo4j transaction.
252
+ condCls (dict): The properties of the condition class node including 'className' and 'code'.
253
+ """
254
+ query = """
255
+ MERGE (n:Condition:LionNode {className: $className})
256
+ SET n.code = $code
257
+ """
258
+ await tx.run(query, className=condCls["class_name"], code=condCls["class"])
259
+
260
+ async def add_node(self, tx, node_dict, structure_name):
261
+ """
262
+ Asynchronously adds various types of nodes to the Neo4j graph based on the provided dictionary of node lists.
263
+
264
+ This method iterates through a dictionary where each key is a node type and each value is a list of nodes.
265
+ It adds each node to the graph according to its type.
266
+
267
+ Args:
268
+ tx: The Neo4j transaction.
269
+ node_dict (dict): A dictionary where keys are node type strings and values are lists of node properties dictionaries.
270
+ structure_name (str): The name of the structure to which these nodes belong, used specifically for 'StructureExecutor' nodes.
271
+
272
+ Raises:
273
+ ValueError: If an unsupported node type is detected in the dictionary.
274
+ """
275
+ for node in node_dict:
276
+ node_list = node_dict[node]
277
+ if node == "StructureExecutor":
278
+ [
279
+ await self.add_structure_node(tx, i, structure_name)
280
+ for i in node_list
281
+ ]
282
+ elif node == "System":
283
+ [await self.add_system_node(tx, i) for i in node_list]
284
+ elif node == "Instruction":
285
+ [await self.add_instruction_node(tx, i) for i in node_list]
286
+ elif node == "Tool":
287
+ [await self.add_tool_node(tx, i) for i in node_list]
288
+ elif node == "ActionSelection":
289
+ [await self.add_actionSelection_node(tx, i) for i in node_list]
290
+ elif node == "BaseAgent":
291
+ [await self.add_baseAgent_node(tx, i) for i in node_list]
292
+ else:
293
+ raise ValueError("Not supported node type detected")
294
+
295
+ async def add_edge(self, tx, edge_list):
296
+ """
297
+ Asynchronously adds edges to the Neo4j graph based on a list of edge properties.
298
+
299
+ This method processes a list of edges, each represented by a dictionary of properties, and creates either
300
+ 'BUNDLE' or 'FORWARD' relationships in the graph based on the 'bundle' property of each edge.
301
+
302
+ Args:
303
+ tx: The Neo4j transaction.
304
+ edge_list (list[dict]): A list of dictionaries where each dictionary contains properties of an edge including
305
+ its type, identifiers of the head and tail nodes, and additional attributes like label and condition.
306
+
307
+ Raises:
308
+ ValueError: If an edge dictionary is missing required properties or contains invalid values.
309
+ """
310
+ for edge in edge_list:
311
+ if edge["bundle"] == "True":
312
+ await self.add_bundle_edge(tx, edge)
313
+ else:
314
+ await self.add_forward_edge(tx, edge)
315
+
316
+ async def add_condition_cls(self, tx, edge_cls_list):
317
+ """
318
+ Asynchronously adds condition class nodes to the Neo4j graph.
319
+
320
+ This method iterates over a list of condition class definitions and adds each as a node in the graph.
321
+ Each condition class is typically used to define logic or conditions within the graph structure.
322
+
323
+ Args:
324
+ tx: The Neo4j transaction.
325
+ edge_cls_list (list[dict]): A list of dictionaries where each dictionary represents the properties of a
326
+ condition class, including the class name and its corresponding code.
327
+
328
+ Raises:
329
+ ValueError: If any condition class dictionary is missing required properties or the properties do not adhere
330
+ to expected formats.
331
+ """
332
+ for cls in edge_cls_list:
333
+ await self.add_single_condition_cls(tx, cls)
334
+
335
+ @staticmethod
336
+ async def check_id_constraint(tx):
337
+ """
338
+ Asynchronously applies a unique constraint on the 'id' attribute for all nodes of type 'LionNode' in the graph.
339
+
340
+ This constraint ensures that each node in the graph has a unique identifier.
341
+
342
+ Args:
343
+ tx: The Neo4j transaction.
344
+ """
345
+ query = """
346
+ CREATE CONSTRAINT node_id IF NOT EXISTS
347
+ FOR (n:LionNode) REQUIRE (n.id) IS UNIQUE
348
+ """
349
+ await tx.run(query)
350
+
351
+ @staticmethod
352
+ async def check_structure_name_constraint(tx):
353
+ """
354
+ Asynchronously applies a unique constraint on the 'name' attribute for all nodes of type 'Structure' in the graph.
355
+
356
+ This constraint ensures that each structure in the graph can be uniquely identified by its name.
357
+
358
+ Args:
359
+ tx: The Neo4j transaction.
360
+ """
361
+ query = """
362
+ CREATE CONSTRAINT structure_name IF NOT EXISTS
363
+ FOR (n:Structure) REQUIRE (n.name) IS UNIQUE
364
+ """
365
+ await tx.run(query)
366
+
367
+ async def store(self, structure, structure_name):
368
+ """
369
+ Asynchronously stores a structure and its components in the Neo4j graph.
370
+
371
+ This method orchestrates the storage of nodes, edges, and other related elements that make up a structure,
372
+ ensuring all elements are added transactionally.
373
+
374
+ Args:
375
+ structure: The structure object containing the nodes and edges to be stored.
376
+ structure_name (str): The name of the structure, used to uniquely identify it in the graph.
377
+
378
+ Raises:
379
+ ValueError: If the transaction fails due to an exception, indicating an issue with the data or constraints.
380
+ """
381
+ node_list, node_dict = output_node_list(structure)
382
+ edge_list, edge_cls_list = output_edge_list(structure)
383
+
384
+ async with self.driver as driver:
385
+ async with driver.session(database=self.database) as session:
386
+ # constraint
387
+ tx = await session.begin_transaction()
388
+ try:
389
+ await self.check_id_constraint(tx)
390
+ await self.check_structure_name_constraint(tx)
391
+ await tx.commit()
392
+ except Exception as e:
393
+ raise ValueError(f"transaction rolled back due to exception: {e}")
394
+ finally:
395
+ await tx.close()
396
+
397
+ # query
398
+ tx = await session.begin_transaction()
399
+ try:
400
+ await self.add_node(tx, node_dict, structure_name)
401
+ await self.add_edge(tx, edge_list)
402
+ await self.add_condition_cls(tx, edge_cls_list)
403
+ await self.add_head_edge(tx, structure)
404
+ await tx.commit()
405
+ except Exception as e:
406
+ raise ValueError(f"transaction rolled back due to exception: {e}")
407
+ finally:
408
+ await tx.close()
409
+
410
+ # ---------------------frpm_neo4j---------------------------------
411
+ @staticmethod
412
+ async def match_node(tx, node_id):
413
+ """
414
+ Asynchronously retrieves a node from the graph based on its identifier.
415
+
416
+ Args:
417
+ tx: The Neo4j transaction.
418
+ node_id (str): The unique identifier of the node to retrieve.
419
+
420
+ Returns:
421
+ A dictionary containing the properties of the node if found, otherwise None.
422
+ """
423
+ query = """
424
+ MATCH (n:LionNode) WHERE n.id = $id
425
+ RETURN labels(n), n{.*}
426
+ """
427
+ result = await tx.run(query, id=node_id)
428
+ result = [record.values() async for record in result]
429
+ if result:
430
+ return result[0]
431
+ else:
432
+ return None
433
+
434
+ @staticmethod
435
+ async def match_structure_id(tx, name):
436
+ """
437
+ Asynchronously retrieves the identifier of a structure based on its name.
438
+
439
+ Args:
440
+ tx: The Neo4j transaction.
441
+ name (str): The name of the structure to retrieve the identifier for.
442
+
443
+ Returns:
444
+ A list containing the identifier(s) of the matching structure(s).
445
+ """
446
+ query = """
447
+ MATCH (n:Structure) WHERE n.name = $name
448
+ RETURN n.id
449
+ """
450
+ result = await tx.run(query, name=name)
451
+ result = [record.values() async for record in result]
452
+ result = sum(result, [])
453
+ return result
454
+
455
+ @staticmethod
456
+ async def head(tx, node_id):
457
+ """
458
+ Asynchronously retrieves the head nodes associated with a structure node in the graph.
459
+
460
+ Args:
461
+ tx: The Neo4j transaction.
462
+ node_id (str): The identifier of the structure node whose head nodes are to be retrieved.
463
+
464
+ Returns:
465
+ A list of dictionaries representing the properties and labels of each head node connected to the structure.
466
+ """
467
+ query = """
468
+ MATCH (n:Structure)-[r:HEAD]->(m) WHERE n.id = $nodeId
469
+ RETURN r{.*}, labels(m), m{.*}
470
+ """
471
+ result = await tx.run(query, nodeId=node_id)
472
+ result = [record.values() async for record in result]
473
+ return result
474
+
475
+ @staticmethod
476
+ async def forward(tx, node_id):
477
+ """
478
+ Asynchronously retrieves all forward relationships and their target nodes for a given node.
479
+
480
+ Args:
481
+ tx: The Neo4j transaction.
482
+ node_id (str): The identifier of the node from which to retrieve forward relationships.
483
+
484
+ Returns:
485
+ A list of dictionaries representing the properties and labels of each node connected by a forward relationship.
486
+ """
487
+ query = """
488
+ MATCH (n:LionNode)-[r:FORWARD]->(m) WHERE n.id = $nodeId
489
+ RETURN r{.*}, labels(m), m{.*}
490
+ """
491
+ result = await tx.run(query, nodeId=node_id)
492
+ result = [record.values() async for record in result]
493
+ return result
494
+
495
+ @staticmethod
496
+ async def bundle(tx, node_id):
497
+ """
498
+ Asynchronously retrieves all bundle relationships and their target nodes for a given node.
499
+
500
+ Args:
501
+ tx: The Neo4j transaction.
502
+ node_id (str): The identifier of the node from which to retrieve bundle relationships.
503
+
504
+ Returns:
505
+ A list of dictionaries representing the properties and labels of each node connected by a bundle relationship.
506
+ """
507
+ query = """
508
+ MATCH (n:LionNode)-[r:BUNDLE]->(m) WHERE n.id = $nodeId
509
+ RETURN labels(m), m{.*}
510
+ """
511
+ result = await tx.run(query, nodeId=node_id)
512
+ result = [record.values() async for record in result]
513
+ return result
514
+
515
+ @staticmethod
516
+ async def match_condition_class(tx, name):
517
+ """
518
+ Asynchronously retrieves the code for a condition class based on its class name.
519
+
520
+ Args:
521
+ tx: The Neo4j transaction.
522
+ name (str): The class name of the condition to retrieve the code for.
523
+
524
+ Returns:
525
+ The code of the condition class if found, otherwise None.
526
+ """
527
+ query = """
528
+ MATCH (n:Condition) WHERE n.className = $name
529
+ RETURN n.code
530
+ """
531
+ result = await tx.run(query, name=name)
532
+ result = [record.values() async for record in result]
533
+ if result:
534
+ return result[0][0]
535
+ else:
536
+ return None
537
+
538
+ async def locate_structure(
539
+ self, tx, structure_name: str = None, structure_id: str = None
540
+ ):
541
+ """
542
+ Asynchronously locates a structure by its name or ID in the Neo4j graph.
543
+
544
+ This method is designed to find a structure either by its name or by a specific identifier,
545
+ returning the identifier if found.
546
+
547
+ Args:
548
+ tx: The Neo4j transaction.
549
+ structure_name (str, optional): The name of the structure to locate.
550
+ structure_id (str, optional): The unique identifier of the structure to locate.
551
+
552
+ Returns:
553
+ str: The identifier of the located structure.
554
+
555
+ Raises:
556
+ ValueError: If neither structure name nor ID is provided, or if the provided name or ID does not correspond
557
+ to any existing structure.
558
+ """
559
+ if not structure_name and not structure_id:
560
+ raise ValueError("Please provide the structure name or id")
561
+ if structure_name:
562
+ id = await self.match_structure_id(tx, structure_name)
563
+ if not id:
564
+ raise ValueError(f"Structure: {structure_name} is not found")
565
+ elif structure_id is not None and structure_id not in id:
566
+ raise ValueError(
567
+ f"{structure_name} and id {structure_id} does not match"
568
+ )
569
+ return id[0]
570
+ else:
571
+ result = await self.match_node(tx, structure_id)
572
+ if result:
573
+ return structure_id
574
+ else:
575
+ raise ValueError(f"Structure id {structure_id} is invalid")
576
+
577
+ async def get_heads(self, structure_name: str = None, structure_id: str = None):
578
+ """
579
+ Asynchronously retrieves the head nodes associated with a given structure in the graph.
580
+
581
+ Args:
582
+ structure_name (str, optional): The name of the structure whose head nodes are to be retrieved.
583
+ structure_id (str, optional): The identifier of the structure whose head nodes are to be retrieved.
584
+
585
+ Returns:
586
+ tuple: A tuple containing the structure identifier and a list of dictionaries, each representing a head node
587
+ connected to the structure.
588
+
589
+ Raises:
590
+ ValueError: If both structure name and ID are not provided, or if the specified structure cannot be found.
591
+ """
592
+ async with self.driver as driver:
593
+ async with driver.session(database=self.database) as session:
594
+ id = await session.execute_read(
595
+ self.locate_structure, structure_name, structure_id
596
+ )
597
+ result = await session.execute_read(self.head, id)
598
+ return id, result
599
+
600
+ async def get_bundle(self, node_id):
601
+ """
602
+ Asynchronously retrieves all nodes connected by a bundle relationship to a given node in the graph.
603
+
604
+ Args:
605
+ node_id (str): The identifier of the node from which bundle relationships are to be retrieved.
606
+
607
+ Returns:
608
+ list: A list of dictionaries representing each node connected by a bundle relationship from the specified node.
609
+ """
610
+ async with self.driver as driver:
611
+ async with driver.session(database=self.database) as session:
612
+ result = await session.execute_read(self.bundle, node_id)
613
+ return result
614
+
615
+ async def get_forwards(self, node_id):
616
+ """
617
+ Asynchronously retrieves all nodes connected by forward relationships to a given node in the graph.
618
+
619
+ Args:
620
+ node_id (str): The identifier of the node from which forward relationships are to be retrieved.
621
+
622
+ Returns:
623
+ list: A list of dictionaries representing each node connected by a forward relationship from the specified node.
624
+ """
625
+ async with self.driver as driver:
626
+ async with driver.session(database=self.database) as session:
627
+ result = await session.execute_read(self.forward, node_id)
628
+ return result
629
+
630
+ async def get_condition_cls_code(self, class_name):
631
+ """
632
+ Asynchronously retrieves the code associated with a specified condition class from the Neo4j graph.
633
+
634
+ This method queries the graph to find the code that defines the behavior or logic of a condition class by its name.
635
+
636
+ Args:
637
+ class_name (str): The name of the condition class whose code is to be retrieved.
638
+
639
+ Returns:
640
+ str: The code of the condition class if found, or None if the class does not exist in the graph.
641
+
642
+ Raises:
643
+ ValueError: If the class_name is not provided or if the query fails due to incorrect syntax or database issues.
644
+ """
645
+ async with self.driver as driver:
646
+ async with driver.session(database=self.database) as session:
647
+ result = await session.execute_read(
648
+ self.match_condition_class, class_name
649
+ )
650
+ return result
651
+
652
+ async def node_exist(self, node_id):
653
+ """
654
+ Asynchronously checks if a node with the specified identifier exists in the Neo4j graph.
655
+
656
+ This method is useful for validation checks before attempting operations that assume the existence of a node.
657
+
658
+ Args:
659
+ node_id (str): The unique identifier of the node to check for existence.
660
+
661
+ Returns:
662
+ bool: True if the node exists in the graph, False otherwise.
663
+
664
+ Raises:
665
+ ValueError: If the node_id is not provided or if the query fails due to incorrect syntax or database issues.
666
+ """
667
+ async with self.driver as driver:
668
+ async with driver.session(database=self.database) as session:
669
+ result = await session.execute_read(self.match_node, node_id)
670
+ if result:
671
+ return True
672
+ else:
673
+ return False