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.
- lionagi/__init__.py +60 -5
- lionagi/core/__init__.py +0 -25
- lionagi/core/_setting/_setting.py +59 -0
- lionagi/core/action/__init__.py +14 -0
- lionagi/core/action/function_calling.py +136 -0
- lionagi/core/action/manual.py +1 -0
- lionagi/core/action/node.py +109 -0
- lionagi/core/action/tool.py +114 -0
- lionagi/core/action/tool_manager.py +356 -0
- lionagi/core/agent/base_agent.py +27 -13
- lionagi/core/agent/eval/evaluator.py +1 -0
- lionagi/core/agent/eval/vote.py +40 -0
- lionagi/core/agent/learn/learner.py +59 -0
- lionagi/core/agent/plan/unit_template.py +1 -0
- lionagi/core/collections/__init__.py +17 -0
- lionagi/core/{generic/data_logger.py → collections/_logger.py} +69 -55
- lionagi/core/collections/abc/__init__.py +53 -0
- lionagi/core/collections/abc/component.py +615 -0
- lionagi/core/collections/abc/concepts.py +297 -0
- lionagi/core/collections/abc/exceptions.py +150 -0
- lionagi/core/collections/abc/util.py +45 -0
- lionagi/core/collections/exchange.py +161 -0
- lionagi/core/collections/flow.py +426 -0
- lionagi/core/collections/model.py +419 -0
- lionagi/core/collections/pile.py +913 -0
- lionagi/core/collections/progression.py +236 -0
- lionagi/core/collections/util.py +64 -0
- lionagi/core/director/direct.py +314 -0
- lionagi/core/director/director.py +2 -0
- lionagi/core/{execute/branch_executor.py → engine/branch_engine.py} +134 -97
- lionagi/core/{execute/instruction_map_executor.py → engine/instruction_map_engine.py} +80 -55
- lionagi/{experimental/directive/evaluator → core/engine}/script_engine.py +17 -1
- lionagi/core/executor/base_executor.py +90 -0
- lionagi/core/{execute/structure_executor.py → executor/graph_executor.py} +62 -66
- lionagi/core/{execute → executor}/neo4j_executor.py +70 -67
- lionagi/core/generic/__init__.py +3 -33
- lionagi/core/generic/edge.py +29 -79
- lionagi/core/generic/edge_condition.py +16 -0
- lionagi/core/generic/graph.py +236 -0
- lionagi/core/generic/hyperedge.py +1 -0
- lionagi/core/generic/node.py +156 -221
- lionagi/core/generic/tree.py +48 -0
- lionagi/core/generic/tree_node.py +79 -0
- lionagi/core/mail/__init__.py +12 -0
- lionagi/core/mail/mail.py +25 -0
- lionagi/core/mail/mail_manager.py +139 -58
- lionagi/core/mail/package.py +45 -0
- lionagi/core/mail/start_mail.py +36 -0
- lionagi/core/message/__init__.py +19 -0
- lionagi/core/message/action_request.py +133 -0
- lionagi/core/message/action_response.py +135 -0
- lionagi/core/message/assistant_response.py +95 -0
- lionagi/core/message/instruction.py +234 -0
- lionagi/core/message/message.py +101 -0
- lionagi/core/message/system.py +86 -0
- lionagi/core/message/util.py +283 -0
- lionagi/core/report/__init__.py +4 -0
- lionagi/core/report/base.py +217 -0
- lionagi/core/report/form.py +231 -0
- lionagi/core/report/report.py +166 -0
- lionagi/core/report/util.py +28 -0
- lionagi/core/rule/_default.py +16 -0
- lionagi/core/rule/action.py +99 -0
- lionagi/core/rule/base.py +238 -0
- lionagi/core/rule/boolean.py +56 -0
- lionagi/core/rule/choice.py +47 -0
- lionagi/core/rule/mapping.py +96 -0
- lionagi/core/rule/number.py +71 -0
- lionagi/core/rule/rulebook.py +109 -0
- lionagi/core/rule/string.py +52 -0
- lionagi/core/rule/util.py +35 -0
- lionagi/core/session/branch.py +431 -0
- lionagi/core/session/directive_mixin.py +287 -0
- lionagi/core/session/session.py +229 -903
- lionagi/core/structure/__init__.py +1 -0
- lionagi/core/structure/chain.py +1 -0
- lionagi/core/structure/forest.py +1 -0
- lionagi/core/structure/graph.py +1 -0
- lionagi/core/structure/tree.py +1 -0
- lionagi/core/unit/__init__.py +5 -0
- lionagi/core/unit/parallel_unit.py +245 -0
- lionagi/core/unit/template/action.py +81 -0
- lionagi/core/unit/template/base.py +51 -0
- lionagi/core/unit/template/plan.py +84 -0
- lionagi/core/unit/template/predict.py +109 -0
- lionagi/core/unit/template/score.py +124 -0
- lionagi/core/unit/template/select.py +104 -0
- lionagi/core/unit/unit.py +362 -0
- lionagi/core/unit/unit_form.py +305 -0
- lionagi/core/unit/unit_mixin.py +1168 -0
- lionagi/core/unit/util.py +71 -0
- lionagi/core/validator/validator.py +364 -0
- lionagi/core/work/work.py +74 -0
- lionagi/core/work/work_function.py +92 -0
- lionagi/core/work/work_queue.py +81 -0
- lionagi/core/work/worker.py +195 -0
- lionagi/core/work/worklog.py +124 -0
- lionagi/experimental/compressor/base.py +46 -0
- lionagi/experimental/compressor/llm_compressor.py +247 -0
- lionagi/experimental/compressor/llm_summarizer.py +61 -0
- lionagi/experimental/compressor/util.py +70 -0
- lionagi/experimental/directive/__init__.py +19 -0
- lionagi/experimental/directive/parser/base_parser.py +69 -2
- lionagi/experimental/directive/{template_ → template}/base_template.py +17 -1
- lionagi/{libs/ln_tokenizer.py → experimental/directive/tokenizer.py} +16 -0
- lionagi/experimental/{directive/evaluator → evaluator}/ast_evaluator.py +16 -0
- lionagi/experimental/{directive/evaluator → evaluator}/base_evaluator.py +16 -0
- lionagi/experimental/knowledge/base.py +10 -0
- lionagi/experimental/memory/__init__.py +0 -0
- lionagi/experimental/strategies/__init__.py +0 -0
- lionagi/experimental/strategies/base.py +1 -0
- lionagi/integrations/bridge/langchain_/documents.py +4 -0
- lionagi/integrations/bridge/llamaindex_/index.py +30 -0
- lionagi/integrations/bridge/llamaindex_/llama_index_bridge.py +6 -0
- lionagi/integrations/chunker/chunk.py +161 -24
- lionagi/integrations/config/oai_configs.py +34 -3
- lionagi/integrations/config/openrouter_configs.py +14 -2
- lionagi/integrations/loader/load.py +122 -21
- lionagi/integrations/loader/load_util.py +6 -77
- lionagi/integrations/provider/_mapping.py +46 -0
- lionagi/integrations/provider/litellm.py +2 -1
- lionagi/integrations/provider/mlx_service.py +16 -9
- lionagi/integrations/provider/oai.py +91 -4
- lionagi/integrations/provider/ollama.py +6 -5
- lionagi/integrations/provider/openrouter.py +115 -8
- lionagi/integrations/provider/services.py +2 -2
- lionagi/integrations/provider/transformers.py +18 -22
- lionagi/integrations/storage/__init__.py +3 -3
- lionagi/integrations/storage/neo4j.py +52 -60
- lionagi/integrations/storage/storage_util.py +44 -46
- lionagi/integrations/storage/structure_excel.py +43 -26
- lionagi/integrations/storage/to_excel.py +11 -4
- lionagi/libs/__init__.py +22 -1
- lionagi/libs/ln_api.py +75 -20
- lionagi/libs/ln_context.py +37 -0
- lionagi/libs/ln_convert.py +21 -9
- lionagi/libs/ln_func_call.py +69 -28
- lionagi/libs/ln_image.py +107 -0
- lionagi/libs/ln_nested.py +26 -11
- lionagi/libs/ln_parse.py +82 -23
- lionagi/libs/ln_queue.py +16 -0
- lionagi/libs/ln_tokenize.py +164 -0
- lionagi/libs/ln_validate.py +16 -0
- lionagi/libs/special_tokens.py +172 -0
- lionagi/libs/sys_util.py +95 -24
- lionagi/lions/coder/code_form.py +13 -0
- lionagi/lions/coder/coder.py +50 -3
- lionagi/lions/coder/util.py +30 -25
- lionagi/tests/libs/test_func_call.py +23 -21
- lionagi/tests/libs/test_nested.py +36 -21
- lionagi/tests/libs/test_parse.py +1 -1
- lionagi/tests/test_core/collections/__init__.py +0 -0
- lionagi/tests/test_core/collections/test_component.py +206 -0
- lionagi/tests/test_core/collections/test_exchange.py +138 -0
- lionagi/tests/test_core/collections/test_flow.py +145 -0
- lionagi/tests/test_core/collections/test_pile.py +171 -0
- lionagi/tests/test_core/collections/test_progression.py +129 -0
- lionagi/tests/test_core/generic/test_edge.py +67 -0
- lionagi/tests/test_core/generic/test_graph.py +96 -0
- lionagi/tests/test_core/generic/test_node.py +106 -0
- lionagi/tests/test_core/generic/test_tree_node.py +73 -0
- lionagi/tests/test_core/test_branch.py +115 -294
- lionagi/tests/test_core/test_form.py +46 -0
- lionagi/tests/test_core/test_report.py +105 -0
- lionagi/tests/test_core/test_validator.py +111 -0
- lionagi/version.py +1 -1
- lionagi-0.2.0.dist-info/LICENSE +202 -0
- lionagi-0.2.0.dist-info/METADATA +272 -0
- lionagi-0.2.0.dist-info/RECORD +240 -0
- lionagi/core/branch/base.py +0 -653
- lionagi/core/branch/branch.py +0 -474
- lionagi/core/branch/flow_mixin.py +0 -96
- lionagi/core/branch/util.py +0 -323
- lionagi/core/direct/__init__.py +0 -19
- lionagi/core/direct/cot.py +0 -123
- lionagi/core/direct/plan.py +0 -164
- lionagi/core/direct/predict.py +0 -166
- lionagi/core/direct/react.py +0 -171
- lionagi/core/direct/score.py +0 -279
- lionagi/core/direct/select.py +0 -170
- lionagi/core/direct/sentiment.py +0 -1
- lionagi/core/direct/utils.py +0 -110
- lionagi/core/direct/vote.py +0 -64
- lionagi/core/execute/base_executor.py +0 -47
- lionagi/core/flow/baseflow.py +0 -23
- lionagi/core/flow/monoflow/ReAct.py +0 -240
- lionagi/core/flow/monoflow/__init__.py +0 -9
- lionagi/core/flow/monoflow/chat.py +0 -95
- lionagi/core/flow/monoflow/chat_mixin.py +0 -253
- lionagi/core/flow/monoflow/followup.py +0 -215
- lionagi/core/flow/polyflow/__init__.py +0 -1
- lionagi/core/flow/polyflow/chat.py +0 -251
- lionagi/core/form/action_form.py +0 -26
- lionagi/core/form/field_validator.py +0 -287
- lionagi/core/form/form.py +0 -302
- lionagi/core/form/mixin.py +0 -214
- lionagi/core/form/scored_form.py +0 -13
- lionagi/core/generic/action.py +0 -26
- lionagi/core/generic/component.py +0 -532
- lionagi/core/generic/condition.py +0 -46
- lionagi/core/generic/mail.py +0 -90
- lionagi/core/generic/mailbox.py +0 -36
- lionagi/core/generic/relation.py +0 -70
- lionagi/core/generic/signal.py +0 -22
- lionagi/core/generic/structure.py +0 -362
- lionagi/core/generic/transfer.py +0 -20
- lionagi/core/generic/work.py +0 -40
- lionagi/core/graph/graph.py +0 -126
- lionagi/core/graph/tree.py +0 -190
- lionagi/core/mail/schema.py +0 -63
- lionagi/core/messages/schema.py +0 -325
- lionagi/core/tool/__init__.py +0 -5
- lionagi/core/tool/tool.py +0 -28
- lionagi/core/tool/tool_manager.py +0 -283
- lionagi/experimental/report/form.py +0 -64
- lionagi/experimental/report/report.py +0 -138
- lionagi/experimental/report/util.py +0 -47
- lionagi/experimental/tool/function_calling.py +0 -43
- lionagi/experimental/tool/manual.py +0 -66
- lionagi/experimental/tool/schema.py +0 -59
- lionagi/experimental/tool/tool_manager.py +0 -138
- lionagi/experimental/tool/util.py +0 -16
- lionagi/experimental/validator/rule.py +0 -139
- lionagi/experimental/validator/validator.py +0 -56
- lionagi/experimental/work/__init__.py +0 -10
- lionagi/experimental/work/async_queue.py +0 -54
- lionagi/experimental/work/schema.py +0 -73
- lionagi/experimental/work/work_function.py +0 -67
- lionagi/experimental/work/worker.py +0 -56
- lionagi/experimental/work2/form.py +0 -371
- lionagi/experimental/work2/report.py +0 -289
- lionagi/experimental/work2/schema.py +0 -30
- lionagi/experimental/work2/tests.py +0 -72
- lionagi/experimental/work2/work_function.py +0 -89
- lionagi/experimental/work2/worker.py +0 -12
- lionagi/integrations/bridge/llamaindex_/get_index.py +0 -294
- lionagi/tests/test_core/generic/test_component.py +0 -89
- lionagi/tests/test_core/test_base_branch.py +0 -426
- lionagi/tests/test_core/test_chat_flow.py +0 -63
- lionagi/tests/test_core/test_mail_manager.py +0 -75
- lionagi/tests/test_core/test_prompts.py +0 -51
- lionagi/tests/test_core/test_session.py +0 -254
- lionagi/tests/test_core/test_session_base_util.py +0 -313
- lionagi/tests/test_core/test_tool_manager.py +0 -95
- lionagi-0.1.2.dist-info/LICENSE +0 -9
- lionagi-0.1.2.dist-info/METADATA +0 -174
- lionagi-0.1.2.dist-info/RECORD +0 -206
- /lionagi/core/{branch → _setting}/__init__.py +0 -0
- /lionagi/core/{execute → agent/eval}/__init__.py +0 -0
- /lionagi/core/{flow → agent/learn}/__init__.py +0 -0
- /lionagi/core/{form → agent/plan}/__init__.py +0 -0
- /lionagi/core/{branch/executable_branch.py → agent/plan/plan.py} +0 -0
- /lionagi/core/{graph → director}/__init__.py +0 -0
- /lionagi/core/{messages → engine}/__init__.py +0 -0
- /lionagi/{experimental/directive/evaluator → core/engine}/sandbox_.py +0 -0
- /lionagi/{experimental/directive/evaluator → core/executor}/__init__.py +0 -0
- /lionagi/{experimental/directive/template_ → core/rule}/__init__.py +0 -0
- /lionagi/{experimental/report → core/unit/template}/__init__.py +0 -0
- /lionagi/{experimental/tool → core/validator}/__init__.py +0 -0
- /lionagi/{experimental/validator → core/work}/__init__.py +0 -0
- /lionagi/experimental/{work2 → compressor}/__init__.py +0 -0
- /lionagi/{core/flow/mono_chat_mixin.py → experimental/directive/template/__init__.py} +0 -0
- /lionagi/experimental/directive/{schema.py → template/schema.py} +0 -0
- /lionagi/experimental/{work2/util.py → evaluator/__init__.py} +0 -0
- /lionagi/experimental/{work2/work.py → knowledge/__init__.py} +0 -0
- /lionagi/{tests/libs/test_async.py → experimental/knowledge/graph.py} +0 -0
- {lionagi-0.1.2.dist-info → lionagi-0.2.0.dist-info}/WHEEL +0 -0
- {lionagi-0.1.2.dist-info → lionagi-0.2.0.dist-info}/top_level.txt +0 -0
lionagi/core/generic/__init__.py
CHANGED
@@ -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 .
|
13
|
-
from .
|
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"]
|
lionagi/core/generic/edge.py
CHANGED
@@ -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
|
12
|
-
from .
|
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
|
-
|
28
|
-
|
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
|
-
|
17
|
+
...,
|
18
|
+
title="Out",
|
38
19
|
description="The identifier of the tail node of the edge.",
|
39
20
|
)
|
40
|
-
|
21
|
+
|
22
|
+
condition: Condition | EdgeCondition | None = Field(
|
41
23
|
default=None,
|
42
24
|
description="Optional condition that must be met for the edge "
|
43
|
-
|
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
|
-
|
55
|
-
|
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
|
-
|
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
|
147
|
-
"""
|
148
|
-
|
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
|
156
|
-
"""
|
157
|
-
|
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
|