lionagi 0.1.2__py3-none-any.whl → 0.2.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.
Files changed (268) hide show
  1. lionagi/__init__.py +60 -5
  2. lionagi/core/__init__.py +0 -25
  3. lionagi/core/_setting/_setting.py +59 -0
  4. lionagi/core/action/__init__.py +14 -0
  5. lionagi/core/action/function_calling.py +136 -0
  6. lionagi/core/action/manual.py +1 -0
  7. lionagi/core/action/node.py +109 -0
  8. lionagi/core/action/tool.py +114 -0
  9. lionagi/core/action/tool_manager.py +356 -0
  10. lionagi/core/agent/base_agent.py +27 -13
  11. lionagi/core/agent/eval/evaluator.py +1 -0
  12. lionagi/core/agent/eval/vote.py +40 -0
  13. lionagi/core/agent/learn/learner.py +59 -0
  14. lionagi/core/agent/plan/unit_template.py +1 -0
  15. lionagi/core/collections/__init__.py +17 -0
  16. lionagi/core/{generic/data_logger.py → collections/_logger.py} +69 -55
  17. lionagi/core/collections/abc/__init__.py +53 -0
  18. lionagi/core/collections/abc/component.py +615 -0
  19. lionagi/core/collections/abc/concepts.py +297 -0
  20. lionagi/core/collections/abc/exceptions.py +150 -0
  21. lionagi/core/collections/abc/util.py +45 -0
  22. lionagi/core/collections/exchange.py +161 -0
  23. lionagi/core/collections/flow.py +426 -0
  24. lionagi/core/collections/model.py +419 -0
  25. lionagi/core/collections/pile.py +913 -0
  26. lionagi/core/collections/progression.py +236 -0
  27. lionagi/core/collections/util.py +64 -0
  28. lionagi/core/director/direct.py +314 -0
  29. lionagi/core/director/director.py +2 -0
  30. lionagi/core/{execute/branch_executor.py → engine/branch_engine.py} +134 -97
  31. lionagi/core/{execute/instruction_map_executor.py → engine/instruction_map_engine.py} +80 -55
  32. lionagi/{experimental/directive/evaluator → core/engine}/script_engine.py +17 -1
  33. lionagi/core/executor/base_executor.py +90 -0
  34. lionagi/core/{execute/structure_executor.py → executor/graph_executor.py} +62 -66
  35. lionagi/core/{execute → executor}/neo4j_executor.py +70 -67
  36. lionagi/core/generic/__init__.py +3 -33
  37. lionagi/core/generic/edge.py +29 -79
  38. lionagi/core/generic/edge_condition.py +16 -0
  39. lionagi/core/generic/graph.py +236 -0
  40. lionagi/core/generic/hyperedge.py +1 -0
  41. lionagi/core/generic/node.py +156 -221
  42. lionagi/core/generic/tree.py +48 -0
  43. lionagi/core/generic/tree_node.py +79 -0
  44. lionagi/core/mail/__init__.py +12 -0
  45. lionagi/core/mail/mail.py +25 -0
  46. lionagi/core/mail/mail_manager.py +139 -58
  47. lionagi/core/mail/package.py +45 -0
  48. lionagi/core/mail/start_mail.py +36 -0
  49. lionagi/core/message/__init__.py +19 -0
  50. lionagi/core/message/action_request.py +133 -0
  51. lionagi/core/message/action_response.py +135 -0
  52. lionagi/core/message/assistant_response.py +95 -0
  53. lionagi/core/message/instruction.py +234 -0
  54. lionagi/core/message/message.py +101 -0
  55. lionagi/core/message/system.py +86 -0
  56. lionagi/core/message/util.py +283 -0
  57. lionagi/core/report/__init__.py +4 -0
  58. lionagi/core/report/base.py +217 -0
  59. lionagi/core/report/form.py +231 -0
  60. lionagi/core/report/report.py +166 -0
  61. lionagi/core/report/util.py +28 -0
  62. lionagi/core/rule/_default.py +16 -0
  63. lionagi/core/rule/action.py +99 -0
  64. lionagi/core/rule/base.py +238 -0
  65. lionagi/core/rule/boolean.py +56 -0
  66. lionagi/core/rule/choice.py +47 -0
  67. lionagi/core/rule/mapping.py +96 -0
  68. lionagi/core/rule/number.py +71 -0
  69. lionagi/core/rule/rulebook.py +109 -0
  70. lionagi/core/rule/string.py +52 -0
  71. lionagi/core/rule/util.py +35 -0
  72. lionagi/core/session/branch.py +431 -0
  73. lionagi/core/session/directive_mixin.py +287 -0
  74. lionagi/core/session/session.py +229 -903
  75. lionagi/core/structure/__init__.py +1 -0
  76. lionagi/core/structure/chain.py +1 -0
  77. lionagi/core/structure/forest.py +1 -0
  78. lionagi/core/structure/graph.py +1 -0
  79. lionagi/core/structure/tree.py +1 -0
  80. lionagi/core/unit/__init__.py +5 -0
  81. lionagi/core/unit/parallel_unit.py +245 -0
  82. lionagi/core/unit/template/action.py +81 -0
  83. lionagi/core/unit/template/base.py +51 -0
  84. lionagi/core/unit/template/plan.py +84 -0
  85. lionagi/core/unit/template/predict.py +109 -0
  86. lionagi/core/unit/template/score.py +124 -0
  87. lionagi/core/unit/template/select.py +104 -0
  88. lionagi/core/unit/unit.py +362 -0
  89. lionagi/core/unit/unit_form.py +305 -0
  90. lionagi/core/unit/unit_mixin.py +1168 -0
  91. lionagi/core/unit/util.py +71 -0
  92. lionagi/core/validator/validator.py +364 -0
  93. lionagi/core/work/work.py +74 -0
  94. lionagi/core/work/work_function.py +92 -0
  95. lionagi/core/work/work_queue.py +81 -0
  96. lionagi/core/work/worker.py +195 -0
  97. lionagi/core/work/worklog.py +124 -0
  98. lionagi/experimental/compressor/base.py +46 -0
  99. lionagi/experimental/compressor/llm_compressor.py +247 -0
  100. lionagi/experimental/compressor/llm_summarizer.py +61 -0
  101. lionagi/experimental/compressor/util.py +70 -0
  102. lionagi/experimental/directive/__init__.py +19 -0
  103. lionagi/experimental/directive/parser/base_parser.py +69 -2
  104. lionagi/experimental/directive/{template_ → template}/base_template.py +17 -1
  105. lionagi/{libs/ln_tokenizer.py → experimental/directive/tokenizer.py} +16 -0
  106. lionagi/experimental/{directive/evaluator → evaluator}/ast_evaluator.py +16 -0
  107. lionagi/experimental/{directive/evaluator → evaluator}/base_evaluator.py +16 -0
  108. lionagi/experimental/knowledge/base.py +10 -0
  109. lionagi/experimental/memory/__init__.py +0 -0
  110. lionagi/experimental/strategies/__init__.py +0 -0
  111. lionagi/experimental/strategies/base.py +1 -0
  112. lionagi/integrations/bridge/langchain_/documents.py +4 -0
  113. lionagi/integrations/bridge/llamaindex_/index.py +30 -0
  114. lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
  115. lionagi/integrations/chunker/chunk.py +161 -24
  116. lionagi/integrations/config/oai_configs.py +34 -3
  117. lionagi/integrations/config/openrouter_configs.py +14 -2
  118. lionagi/integrations/loader/load.py +122 -21
  119. lionagi/integrations/loader/load_util.py +6 -77
  120. lionagi/integrations/provider/_mapping.py +46 -0
  121. lionagi/integrations/provider/litellm.py +2 -1
  122. lionagi/integrations/provider/mlx_service.py +16 -9
  123. lionagi/integrations/provider/oai.py +91 -4
  124. lionagi/integrations/provider/ollama.py +6 -5
  125. lionagi/integrations/provider/openrouter.py +115 -8
  126. lionagi/integrations/provider/services.py +2 -2
  127. lionagi/integrations/provider/transformers.py +18 -22
  128. lionagi/integrations/storage/__init__.py +3 -3
  129. lionagi/integrations/storage/neo4j.py +52 -60
  130. lionagi/integrations/storage/storage_util.py +44 -46
  131. lionagi/integrations/storage/structure_excel.py +43 -26
  132. lionagi/integrations/storage/to_excel.py +11 -4
  133. lionagi/libs/__init__.py +22 -1
  134. lionagi/libs/ln_api.py +75 -20
  135. lionagi/libs/ln_context.py +37 -0
  136. lionagi/libs/ln_convert.py +21 -9
  137. lionagi/libs/ln_func_call.py +69 -28
  138. lionagi/libs/ln_image.py +107 -0
  139. lionagi/libs/ln_nested.py +26 -11
  140. lionagi/libs/ln_parse.py +82 -23
  141. lionagi/libs/ln_queue.py +16 -0
  142. lionagi/libs/ln_tokenize.py +164 -0
  143. lionagi/libs/ln_validate.py +16 -0
  144. lionagi/libs/special_tokens.py +172 -0
  145. lionagi/libs/sys_util.py +95 -24
  146. lionagi/lions/coder/code_form.py +13 -0
  147. lionagi/lions/coder/coder.py +50 -3
  148. lionagi/lions/coder/util.py +30 -25
  149. lionagi/tests/libs/test_func_call.py +23 -21
  150. lionagi/tests/libs/test_nested.py +36 -21
  151. lionagi/tests/libs/test_parse.py +1 -1
  152. lionagi/tests/test_core/collections/__init__.py +0 -0
  153. lionagi/tests/test_core/collections/test_component.py +206 -0
  154. lionagi/tests/test_core/collections/test_exchange.py +138 -0
  155. lionagi/tests/test_core/collections/test_flow.py +145 -0
  156. lionagi/tests/test_core/collections/test_pile.py +171 -0
  157. lionagi/tests/test_core/collections/test_progression.py +129 -0
  158. lionagi/tests/test_core/generic/test_edge.py +67 -0
  159. lionagi/tests/test_core/generic/test_graph.py +96 -0
  160. lionagi/tests/test_core/generic/test_node.py +106 -0
  161. lionagi/tests/test_core/generic/test_tree_node.py +73 -0
  162. lionagi/tests/test_core/test_branch.py +115 -294
  163. lionagi/tests/test_core/test_form.py +46 -0
  164. lionagi/tests/test_core/test_report.py +105 -0
  165. lionagi/tests/test_core/test_validator.py +111 -0
  166. lionagi/version.py +1 -1
  167. lionagi-0.2.0.dist-info/LICENSE +202 -0
  168. lionagi-0.2.0.dist-info/METADATA +272 -0
  169. lionagi-0.2.0.dist-info/RECORD +240 -0
  170. lionagi/core/branch/base.py +0 -653
  171. lionagi/core/branch/branch.py +0 -474
  172. lionagi/core/branch/flow_mixin.py +0 -96
  173. lionagi/core/branch/util.py +0 -323
  174. lionagi/core/direct/__init__.py +0 -19
  175. lionagi/core/direct/cot.py +0 -123
  176. lionagi/core/direct/plan.py +0 -164
  177. lionagi/core/direct/predict.py +0 -166
  178. lionagi/core/direct/react.py +0 -171
  179. lionagi/core/direct/score.py +0 -279
  180. lionagi/core/direct/select.py +0 -170
  181. lionagi/core/direct/sentiment.py +0 -1
  182. lionagi/core/direct/utils.py +0 -110
  183. lionagi/core/direct/vote.py +0 -64
  184. lionagi/core/execute/base_executor.py +0 -47
  185. lionagi/core/flow/baseflow.py +0 -23
  186. lionagi/core/flow/monoflow/ReAct.py +0 -240
  187. lionagi/core/flow/monoflow/__init__.py +0 -9
  188. lionagi/core/flow/monoflow/chat.py +0 -95
  189. lionagi/core/flow/monoflow/chat_mixin.py +0 -253
  190. lionagi/core/flow/monoflow/followup.py +0 -215
  191. lionagi/core/flow/polyflow/__init__.py +0 -1
  192. lionagi/core/flow/polyflow/chat.py +0 -251
  193. lionagi/core/form/action_form.py +0 -26
  194. lionagi/core/form/field_validator.py +0 -287
  195. lionagi/core/form/form.py +0 -302
  196. lionagi/core/form/mixin.py +0 -214
  197. lionagi/core/form/scored_form.py +0 -13
  198. lionagi/core/generic/action.py +0 -26
  199. lionagi/core/generic/component.py +0 -532
  200. lionagi/core/generic/condition.py +0 -46
  201. lionagi/core/generic/mail.py +0 -90
  202. lionagi/core/generic/mailbox.py +0 -36
  203. lionagi/core/generic/relation.py +0 -70
  204. lionagi/core/generic/signal.py +0 -22
  205. lionagi/core/generic/structure.py +0 -362
  206. lionagi/core/generic/transfer.py +0 -20
  207. lionagi/core/generic/work.py +0 -40
  208. lionagi/core/graph/graph.py +0 -126
  209. lionagi/core/graph/tree.py +0 -190
  210. lionagi/core/mail/schema.py +0 -63
  211. lionagi/core/messages/schema.py +0 -325
  212. lionagi/core/tool/__init__.py +0 -5
  213. lionagi/core/tool/tool.py +0 -28
  214. lionagi/core/tool/tool_manager.py +0 -283
  215. lionagi/experimental/report/form.py +0 -64
  216. lionagi/experimental/report/report.py +0 -138
  217. lionagi/experimental/report/util.py +0 -47
  218. lionagi/experimental/tool/function_calling.py +0 -43
  219. lionagi/experimental/tool/manual.py +0 -66
  220. lionagi/experimental/tool/schema.py +0 -59
  221. lionagi/experimental/tool/tool_manager.py +0 -138
  222. lionagi/experimental/tool/util.py +0 -16
  223. lionagi/experimental/validator/rule.py +0 -139
  224. lionagi/experimental/validator/validator.py +0 -56
  225. lionagi/experimental/work/__init__.py +0 -10
  226. lionagi/experimental/work/async_queue.py +0 -54
  227. lionagi/experimental/work/schema.py +0 -73
  228. lionagi/experimental/work/work_function.py +0 -67
  229. lionagi/experimental/work/worker.py +0 -56
  230. lionagi/experimental/work2/form.py +0 -371
  231. lionagi/experimental/work2/report.py +0 -289
  232. lionagi/experimental/work2/schema.py +0 -30
  233. lionagi/experimental/work2/tests.py +0 -72
  234. lionagi/experimental/work2/work_function.py +0 -89
  235. lionagi/experimental/work2/worker.py +0 -12
  236. lionagi/integrations/bridge/llamaindex_/get_index.py +0 -294
  237. lionagi/tests/test_core/generic/test_component.py +0 -89
  238. lionagi/tests/test_core/test_base_branch.py +0 -426
  239. lionagi/tests/test_core/test_chat_flow.py +0 -63
  240. lionagi/tests/test_core/test_mail_manager.py +0 -75
  241. lionagi/tests/test_core/test_prompts.py +0 -51
  242. lionagi/tests/test_core/test_session.py +0 -254
  243. lionagi/tests/test_core/test_session_base_util.py +0 -313
  244. lionagi/tests/test_core/test_tool_manager.py +0 -95
  245. lionagi-0.1.2.dist-info/LICENSE +0 -9
  246. lionagi-0.1.2.dist-info/METADATA +0 -174
  247. lionagi-0.1.2.dist-info/RECORD +0 -206
  248. /lionagi/core/{branch → _setting}/__init__.py +0 -0
  249. /lionagi/core/{execute → agent/eval}/__init__.py +0 -0
  250. /lionagi/core/{flow → agent/learn}/__init__.py +0 -0
  251. /lionagi/core/{form → agent/plan}/__init__.py +0 -0
  252. /lionagi/core/{branch/executable_branch.py → agent/plan/plan.py} +0 -0
  253. /lionagi/core/{graph → director}/__init__.py +0 -0
  254. /lionagi/core/{messages → engine}/__init__.py +0 -0
  255. /lionagi/{experimental/directive/evaluator → core/engine}/sandbox_.py +0 -0
  256. /lionagi/{experimental/directive/evaluator → core/executor}/__init__.py +0 -0
  257. /lionagi/{experimental/directive/template_ → core/rule}/__init__.py +0 -0
  258. /lionagi/{experimental/report → core/unit/template}/__init__.py +0 -0
  259. /lionagi/{experimental/tool → core/validator}/__init__.py +0 -0
  260. /lionagi/{experimental/validator → core/work}/__init__.py +0 -0
  261. /lionagi/experimental/{work2 → compressor}/__init__.py +0 -0
  262. /lionagi/{core/flow/mono_chat_mixin.py → experimental/directive/template/__init__.py} +0 -0
  263. /lionagi/experimental/directive/{schema.py → template/schema.py} +0 -0
  264. /lionagi/experimental/{work2/util.py → evaluator/__init__.py} +0 -0
  265. /lionagi/experimental/{work2/work.py → knowledge/__init__.py} +0 -0
  266. /lionagi/{tests/libs/test_async.py → experimental/knowledge/graph.py} +0 -0
  267. {lionagi-0.1.2.dist-info → lionagi-0.2.0.dist-info}/WHEEL +0 -0
  268. {lionagi-0.1.2.dist-info → lionagi-0.2.0.dist-info}/top_level.txt +0 -0
@@ -1,37 +1,7 @@
1
- from .component import BaseComponent, BaseNode
2
- from .condition import Condition
3
- from .data_logger import DataLogger, DLog
4
- from .signal import Signal, Start
5
- from .mail import Mail, Package
6
- from .mailbox import MailBox
7
1
  from .edge import Edge
8
- from .relation import Relations
9
- from .transfer import Transfer
10
- from .work import Work, Worker
11
2
  from .node import Node
12
- from .structure import BaseStructure
13
- from .action import ActionNode, ActionSelection
3
+ from .graph import Graph
4
+ from .tree import Tree
14
5
 
15
6
 
16
- __all__ = [
17
- "BaseComponent",
18
- "BaseNode",
19
- "BaseStructure",
20
- "BaseWork",
21
- "Condition",
22
- "Edge",
23
- "Mail",
24
- "MailBox",
25
- "Start",
26
- "Package",
27
- "Relations",
28
- "Signal",
29
- "Transfer",
30
- "Node",
31
- "Work",
32
- "Worker",
33
- "ActionNode",
34
- "ActionSelection",
35
- "DataLogger",
36
- "DLog",
37
- ]
7
+ __all__ = ["Edge", "Node", "Graph", "Tree"]
@@ -1,91 +1,51 @@
1
- """
2
- Module for representing conditions and edges between nodes in a graph.
3
-
4
- This module provides the base for creating and managing edges that connect
5
- nodes within a graph. It includes support for conditional edges, allowing
6
- the dynamic evaluation of connections based on custom logic.
7
- """
8
-
9
- from typing import Any
10
1
  from pydantic import Field, field_validator
11
- from .component import BaseComponent
12
- from .condition import Condition
13
-
14
-
15
- class Edge(BaseComponent):
16
- """
17
- Represents an edge between two nodes, potentially with a condition.
2
+ from typing import Any
3
+ from lionagi.core.collections.abc import Component, get_lion_id, LionIDable, Condition
4
+ from lionagi.core.generic.edge_condition import EdgeCondition
18
5
 
19
- Attributes:
20
- head (str): The identifier of the head node of the edge.
21
- tail (str): The identifier of the tail node of the edge.
22
- condition (Condition | None): Optional condition that must be met
23
- for the edge to be considered active.
24
- label (str | None): An optional label for the edge.
25
- bundle (bool): A flag indicating if the edge is bundled.
26
6
 
27
- Methods:
28
- check_condition: Evaluates if the condition is met.
29
- string_condition: Retrieves the condition class source code.
30
- """
7
+ class Edge(Component):
8
+ """Represents a directed edge between two nodes in a graph."""
31
9
 
32
10
  head: str = Field(
11
+ ...,
33
12
  title="Head",
34
13
  description="The identifier of the head node of the edge.",
35
14
  )
15
+
36
16
  tail: str = Field(
37
- title="Tail",
17
+ ...,
18
+ title="Out",
38
19
  description="The identifier of the tail node of the edge.",
39
20
  )
40
- condition: Condition | None = Field(
21
+
22
+ condition: Condition | EdgeCondition | None = Field(
41
23
  default=None,
42
24
  description="Optional condition that must be met for the edge "
43
- "to be considered active.",
25
+ "to be considered active.",
44
26
  )
27
+
45
28
  label: str | None = Field(
46
29
  default=None,
47
30
  description="An optional label for the edge.",
48
31
  )
32
+
49
33
  bundle: bool = Field(
50
34
  default=False,
51
35
  description="A flag indicating if the edge is bundled.",
52
36
  )
53
37
 
54
- @field_validator("head", "tail", mode="before")
55
- def _validate_head_tail(cls, value):
56
- """
57
- Validates head and tail fields to ensure valid node identifiers.
58
-
59
- Args:
60
- value (Any): The value of the field being validated.
61
-
62
- Returns:
63
- str: The validated value, ensuring it is a valid identifier.
64
-
65
- Raises:
66
- ValueError: If the validation fails.
67
- """
68
-
69
- if isinstance(value, BaseComponent):
70
- return value.id_
71
- return value
72
-
73
- def check_condition(self, obj: dict[str, Any]) -> bool:
74
- """
75
- Evaluates if the condition associated with the edge is met.
76
-
77
- Args:
78
- obj (dict[str, Any]): Context for condition evaluation.
79
-
80
- Returns:
81
- bool: True if the condition is met, False otherwise.
82
-
83
- Raises:
84
- ValueError: If the condition is not set.
85
- """
38
+ async def check_condition(self, obj: Any) -> bool:
39
+ """Check if the edge condition is met for the given object."""
86
40
  if not self.condition:
87
41
  raise ValueError("The condition for the edge is not set.")
88
- return self.condition(obj)
42
+ check = await self.condition.applies(obj)
43
+ return check
44
+
45
+ @field_validator("head", "tail", mode="before")
46
+ def _validate_head_tail(cls, value):
47
+ """Validate the head and tail fields."""
48
+ return get_lion_id(value)
89
49
 
90
50
  def string_condition(self):
91
51
  """
@@ -143,20 +103,10 @@ class Edge(BaseComponent):
143
103
  class_code = extract_symbols(cell_code, obj.__name__)[0][0]
144
104
  return class_code
145
105
 
146
- def __str__(self) -> str:
147
- """
148
- Returns a simple string representation of the Edge.
149
- """
150
- return (
151
- f"Edge (id_={self.id_}, from={self.head}, to={self.tail}, "
152
- f"label={self.label})"
153
- )
106
+ def __len__(self):
107
+ """Return the length of the edge (always 1)."""
108
+ return 1
154
109
 
155
- def __repr__(self) -> str:
156
- """
157
- Returns a detailed string representation of the Edge.
158
- """
159
- return (
160
- f"Edge(id_={self.id_}, from={self.head}, to={self.tail}, "
161
- f"label={self.label})"
162
- )
110
+ def __contains__(self, item: LionIDable) -> bool:
111
+ """Check if the given item is the head or tail of the edge."""
112
+ return get_lion_id(item) in (self.head, self.tail)
@@ -0,0 +1,16 @@
1
+ from typing import Any
2
+ from pydantic import Field
3
+ from lionagi.core.collections.abc import Condition
4
+ from pydantic import BaseModel
5
+
6
+
7
+ class EdgeCondition(Condition, BaseModel):
8
+ source: Any = Field(
9
+ title="Source",
10
+ description="The source for condition check",
11
+ )
12
+
13
+ class Config:
14
+ """Model configuration settings."""
15
+
16
+ extra = "allow"
@@ -0,0 +1,236 @@
1
+ import contextlib
2
+ from collections import deque
3
+ from typing import Any
4
+
5
+ from lionagi.libs.ln_convert import to_list
6
+ from lionagi.core.collections.abc import (
7
+ Condition,
8
+ Actionable,
9
+ LionTypeError,
10
+ ItemNotFoundError,
11
+ LionIDable,
12
+ )
13
+ from lionagi.core.collections import pile, Pile
14
+
15
+ from lionagi.core.generic.edge import Edge
16
+ from lionagi.core.generic.node import Node
17
+
18
+
19
+ class Graph(Node):
20
+ """Represents a graph structure with nodes and edges."""
21
+
22
+ internal_nodes: Pile = pile()
23
+
24
+ @property
25
+ def internal_edges(self) -> Pile[Edge]:
26
+ """Return a pile of all edges in the graph."""
27
+ return pile(
28
+ {edge.ln_id: edge for node in self.internal_nodes for edge in node.edges},
29
+ Edge,
30
+ )
31
+
32
+ def is_empty(self) -> bool:
33
+ """Check if the graph is empty (has no nodes)."""
34
+ return self.internal_nodes.is_empty()
35
+
36
+ def clear(self):
37
+ """Clear all nodes and edges from the graph."""
38
+ self.internal_nodes.clear()
39
+
40
+ def add_edge(
41
+ self,
42
+ head: Node,
43
+ tail: Node,
44
+ condition: Condition | None = None,
45
+ bundle=False,
46
+ label=None,
47
+ **kwargs,
48
+ ):
49
+ """Add an edge between two nodes in the graph."""
50
+ if isinstance(head, Actionable):
51
+ raise LionTypeError("Actionable nodes cannot be related as head.")
52
+ if isinstance(tail, Actionable):
53
+ bundle = True
54
+
55
+ self.internal_nodes.include(head)
56
+ self.internal_nodes.include(tail)
57
+
58
+ head.relate(
59
+ tail,
60
+ direction="out",
61
+ condition=condition,
62
+ label=label,
63
+ bundle=bundle,
64
+ **kwargs,
65
+ )
66
+
67
+ def remove_edge(self, edge: Any) -> bool:
68
+ """Remove an edge from the graph."""
69
+ edge = edge if isinstance(edge, list) else [edge]
70
+ for i in edge:
71
+ if i not in self.internal_edges:
72
+ raise ItemNotFoundError(f"Edge {i} does not exist in structure.")
73
+ with contextlib.suppress(ItemNotFoundError):
74
+ self._remove_edge(i)
75
+
76
+ def add_node(self, node: Any) -> None:
77
+ """Add a node to the graph."""
78
+ self.internal_nodes.update(node)
79
+
80
+ def get_node(self, item: LionIDable, default=...):
81
+ """Get a node from the graph by its identifier."""
82
+ return self.internal_nodes.get(item, default)
83
+
84
+ def get_node_edges(
85
+ self,
86
+ node: Node | str,
87
+ direction: str = "both",
88
+ label: list | str = None,
89
+ ) -> Pile[Edge] | None:
90
+ """Get the edges of a node in the specified direction and with the given label."""
91
+ node = self.internal_nodes[node]
92
+ edges = None
93
+ match direction:
94
+ case "both":
95
+ edges = node.edges
96
+ case "head" | "predecessor" | "outgoing" | "out" | "predecessors":
97
+ edges = node.relations["out"]
98
+ case "tail" | "successor" | "incoming" | "in" | "successors":
99
+ edges = node.relations["in"]
100
+
101
+ if label:
102
+ return (
103
+ pile(
104
+ [
105
+ edge
106
+ for edge in edges
107
+ if edge.label in to_list(label, dropna=True, flatten=True)
108
+ ]
109
+ )
110
+ if edges
111
+ else None
112
+ )
113
+ return pile(edges) if edges else None
114
+
115
+ def pop_node(self, item, default=...):
116
+ """Remove and return a node from the graph by its identifier."""
117
+ return self.internal_nodes.pop(item, default)
118
+
119
+ def remove_node(self, item):
120
+ """Remove a node from the graph by its identifier."""
121
+ return self.internal_nodes.remove(item)
122
+
123
+ def _remove_edge(self, edge: Edge | str) -> bool:
124
+ """Remove a specific edge from the graph."""
125
+ if edge not in self.internal_edges:
126
+ raise ItemNotFoundError(f"Edge {edge} does not exist in structure.")
127
+
128
+ edge = self.internal_edges[edge]
129
+ head: Node = self.internal_nodes[edge.head]
130
+ tail: Node = self.internal_nodes[edge.tail]
131
+
132
+ head.unrelate(tail, edge=edge)
133
+ return True
134
+
135
+ def get_heads(self) -> Pile:
136
+ """Get all head nodes in the graph."""
137
+ return pile(
138
+ [
139
+ node
140
+ for node in self.internal_nodes
141
+ if node.relations["in"].is_empty() and not isinstance(node, Actionable)
142
+ ]
143
+ )
144
+
145
+ def is_acyclic(self) -> bool:
146
+ """Check if the graph is acyclic (contains no cycles)."""
147
+ node_ids = list(self.internal_nodes.keys())
148
+ check_deque = deque(node_ids)
149
+ check_dict = {key: 0 for key in node_ids} # 0: not visited, 1: temp, 2: perm
150
+
151
+ def visit(key):
152
+ if check_dict[key] == 2:
153
+ return True
154
+ elif check_dict[key] == 1:
155
+ return False
156
+
157
+ check_dict[key] = 1
158
+
159
+ for edge in self.internal_nodes[key].relations["out"]:
160
+ check = visit(edge.tail)
161
+ if not check:
162
+ return False
163
+
164
+ check_dict[key] = 2
165
+ return True
166
+
167
+ while check_deque:
168
+ key = check_deque.pop()
169
+ check = visit(key)
170
+ if not check:
171
+ return False
172
+ return True
173
+
174
+ def to_networkx(self, **kwargs) -> Any:
175
+ """Convert the graph to a NetworkX graph object."""
176
+ from lionagi.libs import SysUtil
177
+
178
+ SysUtil.check_import("networkx")
179
+
180
+ from networkx import DiGraph
181
+
182
+ g = DiGraph(**kwargs)
183
+ for node in self.internal_nodes:
184
+ node_info = node.to_dict()
185
+ node_info.pop("ln_id")
186
+ node_info.update({"class_name": node.class_name})
187
+ g.add_node(node.ln_id, **node_info)
188
+
189
+ for _edge in self.internal_edges:
190
+ edge_info = _edge.to_dict()
191
+ edge_info.pop("ln_id")
192
+ edge_info.update({"class_name": _edge.class_name})
193
+ source_node_id = edge_info.pop("head")
194
+ target_node_id = edge_info.pop("tail")
195
+ g.add_edge(source_node_id, target_node_id, **edge_info)
196
+
197
+ return g
198
+
199
+ def display(self, **kwargs):
200
+ """Display the graph using NetworkX and Matplotlib."""
201
+ from lionagi.libs import SysUtil
202
+
203
+ SysUtil.check_import("networkx")
204
+ SysUtil.check_import("matplotlib", "pyplot")
205
+
206
+ import networkx as nx
207
+ import matplotlib.pyplot as plt
208
+
209
+ g = self.to_networkx(**kwargs)
210
+ pos = nx.spring_layout(g)
211
+ nx.draw(
212
+ g,
213
+ pos,
214
+ edge_color="black",
215
+ width=1,
216
+ linewidths=1,
217
+ node_size=500,
218
+ node_color="orange",
219
+ alpha=0.9,
220
+ labels=nx.get_node_attributes(g, "class_name"),
221
+ )
222
+
223
+ labels = nx.get_edge_attributes(g, "label")
224
+ labels = {k: v for k, v in labels.items() if v}
225
+
226
+ if labels:
227
+ nx.draw_networkx_edge_labels(
228
+ g, pos, edge_labels=labels, font_color="purple"
229
+ )
230
+
231
+ plt.axis("off")
232
+ plt.show()
233
+
234
+ def size(self) -> int:
235
+ """Return the number of nodes in the graph."""
236
+ return len(self.internal_nodes)
@@ -0,0 +1 @@
1
+ # TODO